最长不含重复字符的子字符串(剑指 Offer 48)
1 问题描述
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
2 解题思路
长度为N的字符串共有(n+1)n/2个子字符串遍历复杂度为O(n²),判断长度为 N 的字符串是否有重复字符的复杂度为 O(N) ,因此本题使用暴力法解决的复杂度为O(n³)。
第一次想到使用HashSet
降低,判断长度为 N 的字符串是否有重复字符的复杂度为O(1),向set中添加,返回false则有重复。因此使用双循环加HashSet
的方式解题时间复杂度为O(n²)。
思想:第一层循环遍历字符串,从s[i]
(起始为0)开始,第二层循环送s[i+1]
开始向set
中添加元素,若返回flase
,记录当前set
大小即为本次不重复子串长度,并清空set
退出第二层循环,用sum
保存set
大小的最大值。进入下一次循环从i+1
开始判断。
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.equals("")||s==null){
return 0;
}
int sum=0;
Set<Character> set = new HashSet<>();
for(int i = 0;i<s.length();i++){
set.add(s.charAt(i));
for(int j = i+1;j<s.length();j++){
if(!set.add(s.charAt(j))){
sum=Math.max(sum,set.size());
set.clear();
break;
}
}
sum=Math.max(sum,set.size());
}
return sum;
}
}
3 改进
使用暴力法时间复杂度太大,因此可以考虑使用动态规划降低时间复杂度。该问题主要就是要找到两个相隔最远的相同字符之间的距离,可以考虑使用HashMap
保存每个字符的下标,遍历到相同字符时保存二者之间的距离并更新HashMap
,最后返回每次保存的距离之间的最大值。
状态定义: 设动态规划列表 dp
,dp[j]
代表以字符 s[j]
为结尾的 “最长不重复子字符串” 的长度。
转移方程: 固定右边界 j
,设字符 s[j]
左边距离最近的相同字符为 s[i]
,即 s[i] = s[i]=s[j]
。
当i<0
,即s[j]
左边无与其相同字符,则 dp[j] = dp[j-1] + 1
;
当 dp[j−1]<j−i
,说明上一个与 s[i]
相同的字符不在本次包含 s[i]
的子字符串 dp[j-1
]中,则 dp[j] = dp[j - 1] + 1
;
当 dp[j−1]≥j−i
,说明上一个与s[i]
想相同的字符在本次包含 s[i]
的子字符串dp[j−1]
区间之中 ,则dp[j]
的左边界由 s[i]
决定,即 dp[j] = j - i
;
最后返回max(dp)
。
由于返回值是取 dp
列表最大值,因此可借助变量 tmp
存储 dp[j]
,变量 res
每轮更新最大值即可。
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.equals("")||s==null){
return 0;
}
int temp = 0;
int res = 0;
HashMap<Character,Integer> map = new HashMap<>();
for(int i = 0;i<s.length();i++){
int j = map.getOrDefault(s.charAt(i),-1);
map.put(s.charAt(i),i);
if(temp<i-j){
temp++;
}else{
temp = i-j;
}
res = Math.max(res,temp);
}
return res;
}
}