题目:
给定一个字符串
s
,请你找出其中不含有重复字符的 最长连续子字符串 的长度。
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子字符串是 "abc",所以其长度为 3。
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子字符串是 "b",所以其长度为 1。
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
输入: s = ""
输出: 0
提示:
0 <= s.length <= 5 * 10^4
s
由英文字母、数字、符号和空格组成
分析:
相似题目:
拆解关键词:
【非重复字符、最长、连续字串】
优先考虑:
【滑动窗口】
想法:
滑动窗口V1:
本题要求寻找符合条件的连续子串,很容易想到使用滑动窗口(双指针)。来判断下,是否可以使用双指针:
- 要求返回的答案下标是连续的吗? ✅
- 每次窗口滑动是有界限的吗?不是那种必须滑动到最后一个下标才可以确定是否为解的吧?✅
- 左右指针都是按照规律或者说是按照寻找解的要求来移动的吗?✅
滑动窗口是为了获取连续局部最优解,同时还不排除存在其他解的情况下。通过指针的移动来寻找每一个可能出现的解。最终得到全局最优解。
以上条件都满足,那么本题完全可以使用滑动窗口来做。
滑动窗口V2:
在移动left指针的时候,每次都是一个一个在移动,其实这里我们要找的就是元素不重复的那个left位置,本质就是要移动right指针,就要移除掉当前窗口内和right指针重复的那一个元素,以及那一个元素前面的全部元素[窗口内的]。
所以现在我们的目标就是定位到left应该在的位置,所以我们应该记录每一个元素出现的位置,这样就可以在下一次该元素再次出现的时候,顺利的移动left指针直接跳过这个元素。
因为要判断元素是否重复,所以我们还想要set的去重功能,但是这里我们还可以使用set吗?left指针要跳跃,那么set的元素在删除remove的时候,该怎么删除呢?所以,在这里我们不能使用set,如果使用set,那么left指针就不好跳跃,就算跳跃了,那set里面的元素也不能跳跃删除的。。
但是这里需要使用一个来记录元素的下标位置,这里使用map来记录每个元素的下标位置
这里尝试使用两个指针的位置来直接获取解。
代码:
滑动窗口V1:
class Solution {
public int lengthOfLongestSubstring(String s) {
//0、如果特殊情况不多的情况下,首先排除特殊情况
if(s.length()<1)return 0; //如果没任何元素 那么返回0
if(s.trim().length()<1) return 1; //如果有元素,但是都是空格,那么就返回1 因为空格也是一个元素
//1、初始化
int len = s.length();
int left = 0;
int right = 0;
int maxLen = -1;
/*如果判断是否出现重复元素 可以使用 HashSet 也可以使用 数组下标代值 这里我使用了set结构*/
HashSet<Character> set = new HashSet<Character>();
//2、窗口滑动 寻求解
/**
梳理什么时候right移动?什么时候left移动?
·如果当前窗口内没有重复元素(包含没有任何元素的情况,比如初始化)的时候,right++
·如果当前窗口发生了元素的重复,那么left++,直到当前不存在重复元素
·重复循环上述步骤,直到窗口大小超出范围
*/
while(right<len){
//如果set不包含新元素 那么一直添加新元素
while(right<len && !set.contains(s.charAt(right))){
set.add(s.charAt(right));
right++;
}
//程序执行到这里,说明要不就是right超出范围,要不就是出现了重复元素
maxLen = Math.max(right-left,maxLen);
if(right==len) return maxLen; //如果此时right已经越界,那么说明从当前left位置开始后面的元素都不重复,可以直接返回结果了
//如果不是越界,那么移动left指针,直到没有重复元素
//循环操作:此时right指针对应的元素与set内元素冲突,为了不再冲突只能移除set集合里面和right对应元素相同的那个元素,因为要保持字串连续,所以从left到set内重复的元素的位置都要移除掉,直到可以让set添加新的元素
while(left<=right && s.charAt(left)!=s.charAt(right)){
set.remove(s.charAt(left));
left++;
}
//此时left的位置恰好就是重复元素所在的位置 再移除掉这个元素 就可以添加新的元素了
set.remove(s.charAt(left++));
}
return maxLen;
}
}
滑动窗口V2:
class Solution {
/**
在移动left指针的时候,每次都是一个一个在移动,其实这里我们要找的就是元素不重复的那个left位置,本质就是要移动right指针,就要移除掉当前窗口内和right指针重复的那一个元素,以及那一个元素前面的全部元素[窗口内的]。
所以现在我们的目标就是定位到left应该在的位置,所以我们应该记录每一个元素出现的位置,这样就可以在下一次该元素再次出现的时候,顺利的移动left指针直接跳过这个元素。
因为要判断元素是否重复,所以我们还想要set的去重功能,但是这里我们还可以使用set吗?left指针要跳跃,那么set的元素在删除remove的时候,该怎么删除呢?所以,在这里我们不能使用set,如果使用set,那么left指针就不好跳跃,就算跳跃了,那set里面的元素也不能跳跃删除的。。
但是这里需要使用一个来记录元素的下标位置,这里使用map来记录每个元素的下标位置
这里尝试使用两个指针的位置来直接获取解。
*/
public int lengthOfLongestSubstring(String s) {
//0、如果特殊情况不多的情况下,首先排除特殊情况
if(s.length()<1)return 0; //如果没任何元素 那么返回0
if(s.trim().length()<1) return 1; //如果有元素,但是都是空格,那么就返回1 因为空格也是一个元素
//1、初始化
int len = s.length();
int left = 0;
int right = 0;
int maxLen = -1;
HashMap<Character,Integer> map = new HashMap<Character,Integer>();
//2、开始循环
while(right<len){
//如果map不包含新元素 那么一直添加新元素 同时记录每个元素的下标位置
while(right<len && !map.containsKey(s.charAt(right))){
map.put(s.charAt(right),right);
right++;
}
//程序执行到这里,说明要不就是right超出范围,要不就是出现了重复元素
maxLen = Math.max(right-left,maxLen);
if(right==len) return maxLen; //如果此时right已经越界,那么说明从当前left位置开始后面的元素都不重复,可以直接返回结果了
//此时出现重复元素了,我们直接移动left
int pos = map.getOrDefault(s.charAt(right),0);
left = Math.max(left,pos+1); //因为left可能之前移动过了,left不可以回退,所以我们取当前left和下标中的较大的那一个
map.remove(s.charAt(right)); //既然已经跳过该位置,那么这个下标就可以清理了
}
return maxLen;
}
}
总结:
使用滑动窗口的场景:
- 要求返回的答案下标是连续的吗? ✅
- 每次窗口滑动是有界限的吗?不是那种必须滑动到最后一个下标才可以确定是否为解的吧?✅
- 左右指针都是按照规律或者说是按照寻找解的要求来移动的吗?✅
大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~😊