题目描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
分析与代码
- 题目要求最长子串,子串就必须是连续的。
- 变量 i 表示当前子串的起始下标,变量 j 表示当前子串的结束下标,变量 res 保存最大长度,即为循环过程中 j - i 出现的最大值。
解法一:滑动窗口
- 我们可以使用 HashSet 来保存已经加入当前子串中的字符。
- 每次遍历,先判断 HashSet 中是否存在当前字符,若不存在,则加入到 HashSet 中,res 更新,j 加1,即向右扩大滑动窗口一位;若存在,HashSet 移出 i 下标的字符,i 加 1,即丢弃滑动窗口最左的一位。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
Set<Character> set = new HashSet<>();
int i = 0, j = 0;
while (j < s.length()) {
if (!set.contains(s.charAt(j))) {
set.add(s.charAt(j));
res = Math.max(res, j - i + 1);
j++;
} else {
set.remove(s.charAt(i++));
}
}
return res;
}
}
解法二:优化的滑动窗口
- 我们也可以使用 HashMap 来优化,保存字符的同时,把该字符的下标也保存。
- 每次遍历,先判断 HashMap 中是否已存在该字符,若存在,我们用 j' 表示该重复字符的前一个下标,我们要改变 i,这次就不是加1加1地改变,而是跳过
[i,j']
这一段,直接移动到 j‘ + 1 ,若不比 i 大,不需移动;即i = Math.max(i, map.get(s.charAt(j)) + 1)
。 - 更新 res,存入当前字符和下标到 HashMap 中,不管是否重复都需要,重复则更新索引,相当于丢弃前一个重复字符,保存该重复字符最后出现的位置。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
Map<Character, Integer> map = new HashMap<>();
int i = 0, j = 0;
while (j < s.length()) {
if (map.containsKey(s.charAt(j))) {
i = Math.max(i, map.get(s.charAt(j)) + 1);
}
res = Math.max(res, j - i + 1);
map.put(s.charAt(j), j);
j++;
}
return res;
}
}
其实也可以不用containsKey(key)
方法判断,getOrDefault(key,defalutValue)
获取,默认值 -1。
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
Map<Character, Integer> map = new HashMap<>();
int i = 0, j = 0;
while (j < s.length()) {
i = Math.max(i, map.getOrDefault(s.charAt(j), -1) + 1);
res = Math.max(res, j - i + 1);
map.put(s.charAt(j), j);
j++;
}
return res;
}
}
解法三:数组代替HashMap
- 我们知道当 key 可为数值且范围够小时,可用数组代替 HashMap,下标当作 key,数组值当作 value。
- 常用表如下:
int [26];int [52];
用于字母,int [128]
用于 ASCII 码,int [256]
用于扩展 ASCII 码,而这题的范围就是128个字符。 - 这里表里保存的直接就是字符的下一个下标,而不是该字符的下标,
ht[s.charAt(j)] = j + 1
;因为数组默认值为0,若保存该字符下标,则i = Math.max(i, ht[s.charAt(j)])
这里的ht[s.charAt(j)]
需要多加 1,而数组默认值为 0,会出现问题。方法二中的 HashMap 保存字符的下一个下标也是可以的。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0;
int[] ht = new int[128];
int i = 0, j = 0;
while (j < s.length()) {
i = Math.max(i, ht[s.charAt(j)]);
res = Math.max(res, j - i + 1);
ht[s.charAt(j)] = j + 1;
j++;
}
return res;
}
}
小结
这道题主要学习了滑动窗口的思想,其实我感觉和双指针有点类似?感觉优化后的滑动窗口才是真正的滑动窗口。
HashMap 在 key 可为数值且范围够小时,可用数组代替。