1 问题
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given “abcabcbb”, the answer is “abc”, which the length is 3.
Given “bbbbb”, the answer is “b”, with the length of 1.
Given “pwwkew”, the answer is “wke”, with the length of 3. Note that the answer must be a substring, “pwke” is a subsequence and not a substring.
2 分析
定理1:长度为n的字符串的所有子字符串的数量为: n(n+1)2
- 证明:以字符串 “abcde” 为例:
以第一个字符 ‘a’ 开头的子字符串个数为 5
以第二个字符 ‘b’ 开头的子字符串个数为 4
以第三个字符 ‘c’ 开头的子字符串个数为 3
以第四个字符 ‘d’ 开头的子字符串个数为 2
以第五个字符 ‘e’ 开头的子字符串个数为 1
根据以上规律可以得出长度为n的字符串的所有子字符串的个数是 1+2+3+...+n=O(n2)
因此,如果列举出所有的子字符串后再判断最大的长度的子字符串,那么效率不够高。通过观察不含重复字符的子字符串的特征有:
令
Si,j
表示以i为起点,以j为终点的不含有重复字符的子字符串(j对应的字符不在子串中, 例如”abcde”中,
S1,3
表示 “ab”),
Si,j+1
的长度与
Si,j
的长度有如下的关系:
- 如果字符 S(j) 包含在 Si,j 中, 那么 Si,j+1 因为含有重复字符而不符合题目的要求
- 如果字符 S(j) 不包含在 Si,j 中, 那么 Si,j+1 的长度等于 Si,j 的长度+1
这样就可以用滑动窗口方法,处理这一类字符串、数组问题。
滑动窗口方法包括两个主要操作
- 向左(右)扩展
- 向左(右)收缩
具体做法请参考:what is sliding window algorithm
3 伪代码
- 令
left, right
代表窗口的左右边界,将left, right
初始化为0 - 初始化空哈希表
hs
,表的键为left,right
表示的窗口中不重复的字符值 - 初始化
maxLen=0
, 表示最长的不重复子串的长度 - 执行以下循环,循环的结束条件是:
right >= len(s)
,s
表示题目给出的字符串
- 判断
s(j)
是不是在hs
中: - 如果是,那么将
s(left)
从hs
中删除, 并执行left=left+1
,将窗口左边界向右收缩 - 如果否,那么将
s(right)
加入hs
中, 并执行right=right+1
, 将窗口右边界向右扩展, 同时更新maxLen = right - left
- 判断
- 在循环结束后,返回
maxLen
。
时间复杂度:在算法执行的过程中,left
,right
分别遍历一遍s
,故时间复杂度是
O(2n)
。
4 Java代码
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;
}
}