给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
提示:
0
0
0 <= s.length <=
5
?
1
0
4
5 * 10^4
5?104
s 由英文字母、数字、符号和空格组
根据题目描述,找出不含有重复字符的最长子串的长度,也可以理解成遍历字符串,以遍历到的位置为起点,计算不含有重复字符的最长字符串长度,遍历完成后,取其中的最大值。
如果按照上面说的方法,每遍历到一个位置,就从该位置往后查找,直到发现重复字符,计算不重复字符串长度,那么时间复杂度为
O
(
N
2
)
O(N^2)
O(N2),空间复杂度为
O
(
N
)
O(N)
O(N)。
public int lengthOfLongestSubstring1(String s) {
int max = 0;
for(int i = 0; i < s.length(); i++) {
Set<Character> set = new HashSet<>();
int j = i;
for(; j < s.length(); j++) {
char a = s.charAt(j);
if(!set.contains(a)) {
set.add(a);
} else {
break;
}
}
max = Math.max(max, j - i);
}
return max;
}
那么,方法一有优化的空见吗?答案是有。
我们可以以示例1的字符串为例,从头开始遍历一次发现规律。
可以发现递增遍历子串的起始位置时,子串的结束位置也是递增的。当我们以第k个位置为起点时,得到的结束点为 r k r_k rk?,而当我们以第k+1个位置为起点时,得到的结束点必然在 r k r_k rk?或者 r k r_k rk?之后。因为在k到 r k r_k rk?之间,不包含重复字符串,那么在k+1到 r k r_k rk?之间,必然也不包含重复字符串。如此这般,我们便可以用滑动窗口的思路,设置左指针和右指针,在遍历字符串时依次扩大左右指针的范围,找到最长的不重复子串。
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int max = 0;
for (int i = 0, j = 0; j < s.length(); j++) {
char a = s.charAt(j);
if (map.containsKey(a)) {
i = Math.max(i, map.get(a));
}
map.put(a, j + 1);
max = Math.max(max, j - i + 1);
}
return max;
}
}
以上代码中,i代表左指针,j代表右指针。map用来记录其中出现的字符,key代表字符,value代表当前右指针的下一个位置。在遍历的过程中,当我们发现重复字符时,左指针需要移动到map中的value位置。需要注意的是以上代码中map没有删除key,所以当map发现重复字符,而当前左指针i已经大于map中的value时,则i的值保持不变。
时间复杂度:
O
(
N
)
O(N)
O(N)
空间复杂度:
O
(
N
)
O(N)
O(N)