时间:2020/03/24
一.题目描述
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是"abc",所以其
长度为 3。
示例2:
输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是"b"
,所以其长度为 1
示例3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
二.算法
1.暴力法
题解:
通过双层循环找出s的所有子串,并对每个字串判定是否含有重复字符,在判定的过程中使用HashSet。使用该方法虽然可以,但是会超过题目要求的时间复杂度。
代码:
class Solution { public int lengthOfLongestSubstring(String s) { if("".equals(s)){ return 0; } else{ int len = s.length(); int num = 1; for(int i = 0; i < len; i++){ for(int j = i + 1; j < len; j++){ if(noRepetition(s, i, j)) num = Math.max(num, j - i + 1); } } return num; } } public boolean noRepetition(String s, int start, int end){ Set<Character> set = new HashSet<>(); for(int i = start; i <= end; i++){ if(set.contains(s.charAt(i))) return false; set.add(s.charAt(i)); } return true; } }
时间复杂度:O(n3)
2. 滑动窗口
概念:
滑动窗口是解决数组以及字符串问题的常用抽象概念。窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即[i, j)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将[i, j)向右滑动1个元素,则它将变为[i+1, j+1)。注意:这里不一定左右边界都加1,可以只是一个边界加1。
题解:
开始时左右边界都为0,并且创建一个HashSet来存放整个窗口,使用HashSet的优点是查找的时间复杂度可以认为是O(1)。然后开始遍历,判断该位置的字符是否在set中,如果不在,需要添加到set中,如果在,则要判断当前窗口和num(即现有最长子串的长度)哪一个大,将大的赋值给num,之后还要让左边界右移一个单位。这里还有需要注意的一点:在循环外需要再判断一次。(这里与官方题解不同,算是一点小的优化)
代码:
class Solution { public int lengthOfLongestSubstring(String s) { int len = s.length(); Set<Character> set = new HashSet<>(); int num = 0, i = 0, j = 0; while(i < len && j < len){ if(!set.contains(s.charAt(j))){ set.add(s.charAt(j++)); } else{ num = Math.max(num, j - i); set.remove(s.charAt(i++)); } } num = Math.max(num, j - i); return num; } }
时间复杂度:O(n)
3.优化的滑动窗口
题解:
该方法在上面方法的基础上改用HashMap来存储字符,这样可以建立从子符到索引的映射,注意:在HashMap中,当键已经存在时,值会被覆盖。就是说,如果s[j]在[i, j)范围内有与当前字符相同的字符,假设该字符的位置是k,我们不需要逐渐增加i,可以直接跳过[i, k]范围内的所有字符,并将i变为k+1。
代码:
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(), ans = 0; Map<Character, Integer> map = new HashMap<>(); // current index of character // try to extend the range [i, j] for (int j = 0, i = 0; j < n; j++) { if (map.containsKey(s.charAt(j))) { i = Math.max(map.get(s.charAt(j)), i); //look } ans = Math.max(ans, j - i + 1); map.put(s.charAt(j), j + 1); } return ans; } }
注意:大家可能会对注释了look的那一行产生疑问,为什么要与i比较并取最大值呢?这里举一个例子:字符串s为abba。你自己在脑海中运行一遍这个例子就知道了。
时间复杂度:O(n)
4.我自己的方法
题解:
这是我做这道题时第一次想到的方法,主要思想就是遍历整个字符串,找到每个位置字符所能组成的最大的不重复的子串,然后取其中的最大长度就是答案。
代码:
class Solution { public int lengthOfLongestSubstring(String s) { int num = 0; if(s == null){ return num; } else{ char[] c = s.toCharArray(); HashMap<Character, Integer> map = new HashMap<>(); int nowNum = 0; int len = c.length; int index = 0; for(int i = 0; i < len; i++){ if(!map.containsKey(c[i])){ nowNum++; map.put(c[i], 1); } else{ if(nowNum > num){ num = nowNum; } nowNum = 0; map.clear(); index++; i = index - 1; // System.out.println(map); } } if(nowNum > num){ num = nowNum; } } return num; } }