1. 链接
. - 力扣(LeetCode)
2. 题目描述
3. 题解
方法一:滑动窗口 + 哈希表
根据题意:
1. 遍历所有可能的子串——利用滑动窗口表示子串;
2. 保证滑动窗口内不包含重复字符——需要哈希表map记录字符出现的下标。
问题:
1. 如何保证每个子串的考虑过了?
2. 如何保证既无重复又是最长?
思路:
1. 设置代表滑动窗口边界的两个指针left、i;
2. 在for循环中,每次都判断以i为右边界的子串(滑动窗口)的最长无重复情况,这就保证了算无遗漏。
3. 每次考虑一个新的右边界i,都要判断滑动窗口内是否包含与右边界相同的字符。左指针left的位置,就是为了保证滑动窗口既无重复又是最长。
4. 根据map中的记录,找到上一个与i字符相同的位置(这里要注意,因为是顺序遍历i,所以在考虑i时,i之前的字符都已经被考虑过了)。如果left不在该字符右侧,说明新加入的i字符已经存在于滑动窗口中,为了保证无重复,left就是该位置的右一位;如果left已经在该位置的右侧,说明虽然之前有和i字符相同的,但是现在已经在滑动窗口之外,不予考虑,left保持不变。
class Solution {
public int lengthOfLongestSubstring(String s) {
int max = 0;
int left = 0; // 1逻辑
Map<Character, Integer> map = new HashMap<>();
char[] ch = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (map.containsKey(ch[i])) {
int newl = map.get(ch[i]);
left = Math.max(left, newl + 1); // 3,4逻辑
}
map.put(ch[i], i); // 2逻辑
max = Math.max(max, i - left + 1);
}
return max;
}
}
方法二:动态规划 + 哈希表
问题:
1. 以i为结尾的子串表示每次的状态,那么前后两次状态是否有联系?
思路:
1. 同方法一的2;
2. 遍历每个字符,左边界left:找到该字符前一次出现的地方 + 1,如果没有就为0;
3. 以i-1字符为结尾的最长无重复子串的长度tmp与i - left + 1(当前字符与上一个重复字符之间的长度)的关系:如果tmp >= i - left + 1,说明以i-1字符为结尾的无重复最长子串中包含了i字符的重复字符,又因为tmp表示的是i-1无重复,所以tmp可以无痛(隐含的left指针就是i字符前一次出现的地方+1)更新为i - left + 1;如果tmp < i - left + 1,说明以i-1字符为结尾子串一定包含在当前left和i之间,所以为了保证最长无重复,以i-1字符为结尾子串直接链接i字符,即 tmp = tmp + 1。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0, len = s.length();
for(int j = 0; j < len; j++) { //i表示左边界,j表示右边界
int i = dic.getOrDefault(s.charAt(j), -1) + 1; //逻辑2
dic.put(s.charAt(j), j);
tmp = tmp < j - i + 1 ? tmp + 1 : j - i + 1; //逻辑3
res = Math.max(res, tmp);
}
return res;
}
}