知识点:
- 转换成字符数组,是字符串问题常见的处理方法。这是因为 charAt 方法,每次访问的时候都会做边界判断,在我们求解的问题中,是不必要的;
- 「滑动窗口」的规则(这里要根据题目意思想出来):要求无重复字符的最长子串,那么需要关注的「状态」就是有重复元素;
- 右边界
right
滑动到刚刚好有重复的时候停下,然后左边界left
滑动到刚刚好没有重复的时候停下。
方法一:滑动窗口
Java 代码:
public class Solution {
public int lengthOfLongestSubstring(String s) {
int len = s.length();
// 特判
if (len < 2) {
return len;
}
char[] charArray = s.toCharArray();
// 当 window 中某个字符的频数为 2 时,表示滑动窗口内有重复字符
int[] count = new int[128];
// 循环不变量:保持不变的性质是:[left, right) 内没有重复元素
int left = 0;
int right = 0;
// 滑动窗口内是否重复
boolean repeating = false;
int res = 1;
while (right < len) {
// 不能写在后面,因为数组下标容易越界
if (count[charArray[right]] == 1) {
repeating = true;
}
count[charArray[right]]++;
right++;
// 此时 [left, right) 内如果有重复元素,就缩小左边界,直到滑窗内没有重复元素
// 否则,让 right 继续右移,这样不会错过最优解
while (repeating) {
if (count[charArray[left]] == 2) {
// 如果满足滑动窗口内有重复的元素,尝试不断删除左边元素
repeating = false;
}
// 只有有重复元素,就得缩短左边界
count[charArray[left]]--;
left++;
}
// 此时 [left, right) 内没有重复元素
res = Math.max(res, right - left);
}
return res;
}
}
方法二:滑动窗口 + 哈希表
思路:使用哈希表记录上一次的位置,左边界直接来到不重复的位置。
-
非空的时候,结果至少是 1 ,因此初值
res
可以设置成为 1; -
右边界没有重复的时候,直接向右边扩张就好了;
-
右边界有重复的时候,只要在滑动窗口内,我们就得更新;
-
如果在滑动窗口之外,一定是之前被计算过的;
-
下一个不重复的子串至少在之前重复的那个位置之后。
Java 代码:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int lengthOfLongestSubstring(String s) {
int len = s.length();
if (len < 2) {
return len;
}
char[] charArray = s.toCharArray();
int left = 0;
int right = 0;
// key 为字符,val 记录了当前读到的字符的下标,同样的字符保留最后的
Map<Character, Integer> map = new HashMap<>(len);
int res = 1;
while (right < len){
if (map.containsKey(charArray[right])) {
// 注意这里是大于等于
if (map.get(charArray[right]) >= left) {
// 注意:快在这个地方,左边界直接跳到之前重复的那个位置之后
left = map.get(charArray[right]) + 1;
}
}
// 无论如何都更新位置
map.put(charArray[right], right);
right++;
// 此时滑动窗口内一定没有重复元素
res = Math.max(res, right - left);
}
return res;
}
}
Java 代码:
public class Solution2 {
// 也可以用数组代替滑动窗口
public int lengthOfLongestSubstring(String s) {
// 重复元素上一次出现的位置很重要
int len = s.length();
if (len < 2) {
return len;
}
int[] window = new int[128];
for (int i = 0; i < 128; i++) {
window[i] = -1;
}
char[] charArray = s.toCharArray();
int res = 1;
int left = 0;
for (int i = 0; i < len; i++) {
if (window[charArray[i]] != -1) {
left = Math.max(left, window[charArray[i]] + 1);
}
window[charArray[i]] = i;
// 注意理解这里为什么是 + 1
res = Math.max(res, i - left + 1);
}
return res;
}
}