LeetCode-3. Longest Substring Without Repeating Characters(滑动窗口)
- 暴力O(N3)
- 普通滑动窗口
- 优化滑动窗口
题目链接
题目
暴力O(N3)
方法:
- 使用一个函数isAllUnique来判断某一段字符串是不是有重复的字符;
- 然后枚举左右边界,一段一段的判断,时间复杂度O(N3);
//暴力
public int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0)
return 0;
int res = 0;
for(int l = 0; l < s.length(); l++){
for(int r = l ; r < s.length(); r++){//注意从l开始,可以是长度为1
if(isAllUnique(s,l,r))
res = Math.max(res,r - l + 1);
}
}
return res;
}
//判断[L,R]之间的字符是不是都是不重复的
public boolean isAllUnique(String s,int L,int R){
Set<Character>set = new HashSet<>();
for(int i = L; i <= R; i++){
if( !set.contains(s.charAt(i)) )
set.add(s.charAt(i));
else
return false;
}
return true;
}
普通滑动窗口
和普通的滑动窗口一样,使用一个freq数组来保存字符出现的次数:
- 每次试探++R,要判断是否越界,然后判断如果前面的字符串段中没有重复的话,R就可以继续扩展,对应s.charAt(++R)上的频次要++;
- 如果有重复的话,左边界L就扩展,此时对应的频次要–;
// 普通滑动窗口
public int lengthOfLongestSubstring(String s) {
if(s.length() == 0 || s == null)
return 0;
char[] str = s.toCharArray();
int[] freq = new int[256];
int L = 0, R = -1,res = 0;
while(R < str.length){
if(R + 1 == str.length)//一定要break 不然L不会break; R你都到str.length - 1,L你要再移动也不会更长了
break;
if(freq[str[R+1]] == 0)
freq[str[++R]]++;
else
freq[str[L++]]--;
res = Math.max(res,R-L+1);
}
return res;
}
对于R边界的判断以及循环的终止,因为R如果到达了边界的话,此时L你再往右边扩展,此时的长度只会更小,所以上述代码也可以简写成下面的样子:
// 普通滑动窗口
public int lengthOfLongestSubstring(String s) {
if(s.length() == 0 || s == null)
return 0;
char[] str = s.toCharArray();
int[] freq = new int[256];
int L = 0, R = -1,res = 0;
while(R+1 < str.length){
if(freq[str[R+1]] == 0)
freq[str[++R]]++;
else
freq[str[L++]]--;
res = Math.max(res,R-L+1);
}
return res;
}
优化滑动窗口
更加优化的方式是:
- 每次更新L的时候,不是简单的只移动一个位置,使用一个last[i]记录i最后一次出现的位置;
- 然后当扩展R的时候,如果前面有重复的,就可以让L扩展到last[s.charAt( R )] + 1的位置;
- 然后记得每次都要更新last[s.charAt( R )]的值;
// 优化的滑动窗口
// 其中使用last[c]保存字符c上一次出现的位置, 用于在右边界发现重复字符时, 快速移动左边界
// 使用这种方法, 时间复杂度依然为O(n), 但是只需要动r指针, 实际上对整个s只遍历了一次
public int lengthOfLongestSubstring(String s) {
if(s == null || s.length() == 0)
return 0;
int L = 0,R = -1,res = 0;
int[] last = new int[256]; //保存s[i] 上一次出现的位置
Arrays.fill(last,-1); // 标记
while(R+1 < s.length()){
R++;
if(last[s.charAt(R)] != -1){ //有重复的,此时快速的移动L
L = Math.max(L,last[s.charAt(R)] + 1);
}
last[s.charAt(R)] = R;
res = Math.max(res,R-L+1);
}
return res;
}