LeetCode 3. 无重复字符的最长子串
题目描述
分析:
本题为找某个字符串中无重复字符的最长字串,和另外一道题是最长连续不重复子序列很接近,只不过本题中最小元素为char不是int,这就不能使用数组下标进行哈希,而要借助字典进行映射。
解决本题主要借助双指针的思想,用左右两个指针
i
、
j
i、j
i、j来维护一个区间,使其尽可能长的包含没有重复的字符同时在每次移动指针后都记录当前区间的长度,遍历完所有元素后就可以得到整个字符串的最长无重复子串,具体分析如下:
- 若当前区间无重复项,满足条件,则令左边的指针 j j j不断向右移动
- 若当前区间有重复项,说明不满足条件,这一定是右指针刚指到的元素出现了重复,此时右指针指向的字符出现的次数为2。可以舍弃掉从左指针到右指针之间的所有元素。为什么?假设当前最长的子串是 [ i , j − 1 ] [i,j-1] [i,j−1]这个区间即 m a x = j − 1 − i + 1 max=j-1-i+1 max=j−1−i+1, j j j当前的指向出现了重复,那么对于任意的 k k k,其中 i + 1 < = k < = j i+1<=k<=j i+1<=k<=j,能够构成的子串的长度一定是小于等于 j − i − 1 + 1 j-i-1+1 j−i−1+1的,因此没有重复计算的必要,只要关注右指针的后面还未比较的元素即可。
grq:对于当前判断的区间
[
i
,
j
]
[i,j]
[i,j],我们的目标是找一个对于j最长的i,使得在该区间内不包含重复字符。如果直接枚举的话找i是需要
O
(
n
)
O(n)
O(n)的,整体是一个
n
2
n^2
n2的复杂度。因此要对其进行优化:假设现在
[
i
,
j
]
[i,j]
[i,j],其中的
i
i
i变成了
j
′
j'
j′,而
j
′
j'
j′也会有一个最靠左的
i
′
i'
i′。从直觉上说
j
′
j'
j′往后移动了,那么对应的
i
′
i'
i′也会往后移动。因该是在i的右边或者是
i
i
i的位置上。
这个是否一定成立呢?反证法:假设
j
′
j'
j′对应的
i
′
i'
i′是在
i
i
i的左边,也就是从
j
′
j'
j′到
i
′
i'
i′之间是不包含重复字符的,这样就会有矛盾,由不包含重复字符的空间
[
i
′
,
j
′
]
=
>
[
i
′
,
j
]
[i',j']=>[i',j]
[i′,j′]=>[i′,j]。相当于说i可以移动到
i
′
i'
i′,而
i
′
<
i
i'<i
i′<i,但在j移动到
j
′
j'
j′的上一步,我们刚刚计算出最长不重复字串为
[
i
,
j
]
[i,j]
[i,j],此时i’是重复的,因此是矛盾的。
这样就方便多了,每次
j
j
j向后移动时,
i
i
i左边的位置不需要再考虑,只是看其是否是原地不动还是也是向后移动。
之后用哈希表来存储区间中每个字符出现的次数,当一个新的j’出现的次数大于2时,那么重复的一定是
s
[
j
′
]
s[j']
s[j′]这个字符了,就要从i开始在哈希表中递减,直到减到之前出现的
s
[
j
′
]
s[j']
s[j′]这个字符。
grq:想象一个滑动窗口,左右框起来当前的不重复子串。当右框框进来了重复值时,左框就要右移,保证框内无重复字符。
当每个字符都能求解到以其为右框的不重复子串时,遍历整个字符串就能求到整个字符串的最长不重复子串。
代码(Java)
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> hash = new HashMap<>();
int res = 0;
for (int i = 0, j = 0; j < s.length(); j ++) {
char c = s.charAt(j);
if (!hash.containsKey(c)) {
hash.put(c, 1);
} else {
hash.put(c, hash.get(c) + 1);
}
while (hash.get(c) > 1) {
hash.put(s.charAt(i), hash.get(s.charAt(i)) - 1);
i ++;
}
res = Math.max(res, j - i + 1);
}
return res;
}
}