一、题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
二、解决
1、动态规划+哈希
思路:(不好理解,详细可看参考1中的幻灯片)
1、状态定义
dp[j] : 以字符s[j]为结尾的“最长不重复字符串”的长度。
2、转移方程
固定右边界j,左边界为i,i从-1开始。
设s[i]=s[j],即字符s[j]左边距离最近相同字符为s[i]。
1)当 i<0,即s[j]左边无相同字符,则dp[j] = dp[j-1] + 1;
2)当dp[j-1] < j-i,(临界值--max{dp[j-1]} = j-i)说明字符s[i]在子字符串dp[j-1] /*区间之外*/,则dp[j] = dp[j-1] + 1。
1)&2)可推出:当i<0,由于dp[j-1]<=j恒成立,因为dp[j-1]<j-i恒成立,即顺利合并1)2)。
3)当dp[j-1]>=j-i, 说明字符s[i]在字符串dp[j-1] /*区间之中*/,则dp[j]左边界由s[i]决定,即dp[j]=j-i.
dp[j] = { dp[j-1]+1, dp[j-1] < j-i }
{ j-i, dp[j-1] >= j-i }
说明:
j | 0 | 1 | 2 | 3 |
---|---|---|---|---|
字符 | a | a | b | a |
dp[j] | 1 | 1 | 2 | 2 |
2)当dp[j-1] < j-i,(临界值--max{dp[j-1]} = j-i)说明字符s[i]在子字符串dp[j-1] /*区间之外*/,则dp[j] = dp[j-1] + 1。
- 当
j = 2,dp[j-1] = dp[2-1] = dp[1] = 1
,字符a(第一个a [i = 0]
)在字符串dp[j-1] = 1(ab [j = 1 && j = 2])
的区间之外。
3)当dp[j-1]>=j-i, 说明字符s[i]在字符串dp[j-1] /*区间之中*/,则dp[j]左边界由s[i]决定,即dp[j]=j-i.
- 当
j = 3, dp[j-1] = dp[2] = 2 >= 3-1
,说明字符a(i = 1)
在字符串dp[j-1] = dp[2] = {ab [i=1 && j-1=2}
区间之中。
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
int res = 0, curRes = 0;
HashMap<Character, Integer> dic = new HashMap<>();
for (int cur = 0; cur < s.length(); cur++) {
int pre = dic.getOrDefault(s.charAt(cur), -1); // 获取索引 pre
dic.put(s.charAt(cur), cur); // 更新哈希表
curRes = curRes < cur - pre ? curRes+1 : cur - pre;
res = Math.max(res, curRes);
}
return res;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n),n为字符串长度
空间复杂度:
O
(
1
)
O(1)
O(1),字符ASCII码范围0~127,最多使用O(128)=O(1)大小的额外空间。
2、双指针+哈希表
思路:
与方法1类似,不同点在于左边界 i 的定义。
- 哈希表dic统计:指针 j 遍历字符 s,哈希表统计字符 s[j] 最后一次出现的索引。
- 左指针 i:根据上轮左指针 i 和 dic[s[j]],每轮更新左边界 i,保证区间 [i+1, j]内无重复字符且最大。 i = m a x ( d i c ( [ s [ j ] ] , i ) i = max(dic([s[j]], i) i=max(dic([s[j]],i)
- 更新结果 res:取上轮res 和 本轮双指针区间[i+1, j]的宽度(即j-i)中的最大值。
r e s = m a x ( r e s , j − i ) res = max(res, j-i) res=max(res,j−i)
代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int pre = -1, res = 0;
for(int cur = 0; cur < s.length(); cur++) {
if(dic.containsKey(s.charAt(cur)))
pre = Math.max(pre, dic.get(s.charAt(cur))); // 更新左指针 i 最大值:abba,读到第二个a,cur=3时,pre能更新到1,否则pre=0,最后res是3,不对了。
dic.put(s.charAt(cur), cur); // 哈希表记录
res = Math.max(res, cur - pre); // 更新结果
}
return res;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1) =
O
(
128
)
O(128)
O(128)
三、参考
1、面试题48. 最长不含重复字符的子字符串(动态规划 / 双指针 + 哈希表,清晰图解)
2、无重复字符的最长子串
3、画解算法:3. 无重复字符的最长子串
4、11-line simple Java solution, O(n) with explanation
5、Share my Java solution using HashSet
6、Shortest O(n) DP solution with explanations