给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
解答:
1.滑动窗口 HashSet
时间复杂度:O(2n) = O(n),空间复杂度:O(min(m, n))
public static int lengthOfLongestSubstring(String s) {
Set<Character> substr = new HashSet<>();
//一定需要两个变量i和j,表示字符串开始和结束的索引
int j = 0,i=0,max=0;// include length=0
while (j < s.length()&&i<s.length()) {
if (!substr.contains(s.charAt(j))) {
//通过j不断地遍历S,重复串时会停留。
substr.add(s.charAt(j++));
//即使i不断地变化,长度会通过max保存
max = Math.max(max, j - i);
} else {
//当出现重复字符时,开始索引向前滑动
substr.remove(s.charAt(i++));
}
}
return max;
}
2.优化滑动窗口 HashMap
时间复杂度:O(n),空间复杂度:O(min(m, n))。被进一步优化为仅需要 n 个步骤。
public static int lengthOfLongestSubstring(String s) {
int n = s.length(), ans = 0;
Map<Character, Integer> map = new HashMap<>(); // current index of character
// try to extend the range [i, j]
for (int j = 0, i = 0; j < n; j++) {
if (map.containsKey(s.charAt(j))) {
//发现重复子串时,i直接变为该重复字符上次出现的位置+1
i = Math.max(map.get(s.charAt(j)), i);
}
//不断地获取长度,即使出现重复字符后,i增大,自然ans减小
ans = Math.max(ans, j - i + 1);
//map不断放值,和size无关,只和索引有关
map.put(s.charAt(j), j + 1);
System.out.println("j="+j+";i="+i);
}
return ans;
}
知识点:
1.滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)[i,j) 向右滑动 1个元素,则它将变为 [i+1, j+1)(左闭,右开)。
2.字符集用数组表示时,常用的表如下所示:
int [26] 用于字母 ‘a’ - ‘z’或 ‘A’ - ‘Z’
int [128] 用于ASCII码
int [256] 用于扩展ASCII码