题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 **最长 **
子串
****的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。
提示:
0 <= s.length <= 5 * 104s由英文字母、数字、符号和空格组成
滑动窗口
解题思路:定义左右指针l、r,分别用于指向字符串s中不重复子串的头部和尾部,代表一个滑动窗口,指针r需要不断向右移动以探寻该窗口内不重复字符串的最大长度,于是就需要判断r所在的字符是否与窗口中的其他字符发生了重复。
判断重复有两种方法:1、遍历窗口中的其他字符,时间复杂度为O(n);2、采用支持通过被存储的字符本身进行随机读取的数据结构存储窗口中的其他字符,时间复杂度为O(1)。我们选择使用更高效的方法2。
那么是否存在方法2中所描述的数据结构呢?我们想到了哈希集合工具,即Java中的HashSet。
而对于指针l、r,即窗口中的子串,如果r所指的字符与窗口中其他字符发生了重复,那么从窗口中任意位置开始的不重复子串长度都只会小于当前窗口中的不重复子串,即当前窗口(不包括重复的r)中的不重复子串长度即为当前窗口的最大不重复子串长度。此时寻找到窗口中哪一个字符与r所指的字符发生了重复,该字符所在位置的向右1位即为下一个窗口的起始位置。
因此算法思路为:指针r在向右移动的同时判断HashSet中是否存在与s[r]相同字符,若不存在则将s[r]存入HashSet中;否则指针l开始向右移动,移动的同时移除HashSet中与l[l]相同的字符,当l停止移动时,s[l+1]必然是原先窗口中与s[r]重复字符,即新的不重复子串的开头。
基于以上分析,最终代码如下:
public int lengthOfLongestSubstring(String s) {
// 判断字符串长度,0或1直接返回长度即可
if(s.length()<=1){
return s.length();
}
int l = 0, r = 1;
HashSet<Character> set = new HashSet<>();
set.add(s.charAt(l));
int res = 0;
while(r<s.length()){
// r所指字符不与HashSet中的字符重复,则存入HashSet,r右移
while(r<s.length() && !set.contains(s.charAt(r))){
set.add(s.charAt(r));
r++;
}
// r所指字符与HashSet中的字符重复,则保存当前不重复子串长度
res = Math.max(res, r-l);
// 从HashSet中移除l所指字符,l右移,直到HashSet中没有与r所指字符重复的字符
set.remove(s.charAt(l));
l++;
}
return res;
}