给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
input: "abcabcbb"
output: 3
input: "bbbbb"
output: 1
input: "pwwkew"
output: 3
class Solution:
def lengthOfLongestSubstring(self, s:str) -> int:
maxlen = 0;
slist = list(s)
nums = len(slist)
for i in range(0, nums):
for j in range(nums - i, -1, -1) :
tmp = slist[i : (i + j)]
k = len(tmp)
if(k > len(set(tmp))):
continue;
else:
if(k > maxlen):
maxlen = k
return maxlen
之所以暴力,是因为这种算法遍历了所有的子序列,判断是否有重复元素。
时间复杂度为O(N^2)
, 这种方法用两层循环遍历了所有的子字符串,并且使用set
判断每个字符串的元素是不是都是唯一的。空间复杂度为O(N)
,因为我们需要用一个set
来判断是不是有重复字符。
为什么这么慢呢?因为每个子字符串里未出现重复元素之前的元素都被重复判断了, 以'abca'
为例,子序列'abc'
已经被判断过没有重复元素了,但是在判断'abca'
时仍会将前3个元素查找一遍,耗费大量时间。实际上只需要查找新的元素a
是否已经出现过了即可,由此引出了滑动窗口技术。
class Solution:
def lengthOfLongestSubstring(self, s:str) -> int:
maxlen, i, j = 0, 0 ,0
c_set = set()
while(j < len(s)):
if(s[j] not in c_set):
c_set.add(s[j])
j = j + 1
maxlen = max(maxlen, j - i)
else:
c_set.remove(s[i])
i = i + 1
return maxlen
关键在于怎么判断滑动窗口新增字符是否已经出现过了,这里可以使用set
。如果新增字符不在前一子序列的set
里,那么将滑动窗口右侧边界j
加一,否则,移除位于最左边界的字符,左边界i
加一。
这里有个小问题,左边界i
会大于右边界j
吗?每次集合增大都会使得j
右移,每次集合减少都会使得i
右移,但是集合增大之后才会导致集合减少,所以不会超过。
滑动窗口的目的就在于消除内层循环,在最差情况下(比如'aaaa'
),i
和j
会经过每一个字符,此时时间复杂度最差为O(2N) = O(N)
。空间的花销在于set
的使用,set
最大长度等于字符集的大小,即O(N)
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int ans = 0, i = 0, j = 0;
while (i < n && j < n) {
// try to extend the range [i, j]
if (!set.contains(s.charAt(j))){
set.add(s.charAt(j++));
ans = Math.max(ans, j - i);
}
else {
set.remove(s.charAt(i++));
}
}
return ans;
}
因为没有使用过 Java 的 HashSet
,出于学习的目的,所以这里给出官方使用HashSet
解题的代码,原理同上。
HashSet 博客参考:
class Solution:
def lengthOfLongestSubstring(self, s:str) -> int:
maxlen = 0
d = {}
count = 0
for i in range(0, len(s)):
if(s[i] in d):
count = max(count, d[s[i]])
d[s[i]] = i + 1;
maxlen = max(maxlen, i - count + 1)
return maxlen
其主要思想依然还是滑动窗口, 但经过了优化。之前的滑动窗口每次遇到重复字符串左边界只会往右移动一格,而优化后会直接跳过整个重复区域。在这里i
作为右边界,而count
作为左边界,始终指向两个重复字符中左字符的位置(比如cgbafbjklb
,当i
到第二个b
时,count
会直接到索引为 2 的位置, 当i
到第三个b
时,count
会直接到索引为 5 的位置)。这里关键在于count
, 只要遇到重复字符,count
就会更新。除此之外,HashTable
的key
对应的value
不是它们本身的索引,而是索引加一,并且求maxlen
时窗口大小也要加一,本质上是因为i
是从 0 开始的,当遇到类似'b'
的单字符串时,i - count = 0
, 与实际不符。
时间复杂度为O(N)
, 空间复杂度为也为O(min(m,n))
, m
为字符集大小,n
为字符串长度。