力扣 - 剑指 Offer 48. 最长不含重复字符的子字符串
题目
思路1(动态规划+哈希表)
- 暴力查找的所花时间复杂度为\(O(N^3)\),太高了,不推荐
- 这题我们可以使用动态规划,我们定义
dp[j]为:以第j个字符为结尾的不包含重复字符的子字符串的最大长度。我们需要从左到右依次扫描字符串中的每个字符,同时使用哈希表存储每个字符出现的最后一次位置,如果第一次出现,就记为-1,因此总共可能出现的情况如下:- 如果第
j个字符之前没有出现过(j < 0),那么dp[j] = dp[j-1] + 1,即没出现过字符,那么我们长度可以直接在前一个基础上直接加上1 - 我们记第
j个字符上一次出现的位置和当前位置的距离为d(j-i),如果第j个字符在此之前出现过,那么又分两种情况:d > dp[j-1],即上一个重复的字符在dp[j-1]区间外,这对当前没有影响,因此dp[j] = dp[j-1] + 1d < dp[j-1],上一个重复的字符在dp[j-1]区间内,这时候,因为要不重复嘛,所以dp[j] = j - i
- 如果第
- 因此,根据上面的推到,我们可以得到状态转移方程:\(dp(j)= \begin{cases} dp[j-1]+1& \text{dp[j - 1] < j - i}\\ j-i& \text{dp[j - 1] <= j - i} \end{cases}\)
- 因为我们是需要最大值,所以可以将
dp数组优化成使用temp存储,使用res从temp中记录最大值
代码
class Solution {
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> map = new HashMap<>();
char[] cs = s.toCharArray();
int length = s.length();
int temp = 0;
int res = 0;
for (int j = 0; j < length; j++) {
// 获取当前字符上一次的出现位置,第一次的话返回-1
int i = map.getOrDefault(cs[j], -1);
// 更新字符出现的位置
map.put(cs[j], j);
// 得到以当前字符结尾的最大长度
temp = temp < j - i ? temp + 1 : j - i;
// res选择最大值
res = Math.max(res, temp);
}
return res;
}
}
复杂度分析
- 时间复杂度:\(O(N)\)
- 空间复杂度:\(O(1)\)
思路2(滑动窗口)
- 定义个一个
start用于控制滑动窗口的左边界(start只能往右不能往左) - 从左到右遍历数组,同时用哈希表记录每个字符最后出现的位置,然后每次将窗口的大小与res比较获取较大的
- 因此
start ~ i区间内无重复字符的窗口大小为i - start(start = Math.max(start, map.get(cs[i]));这个保证了有重复的就会更新start跳过)
代码
class Solution {
public int lengthOfLongestSubstring(String s) {
char[] cs = s.toCharArray();
int length = s.length();
HashMap<Character, Integer> map = new HashMap<>();
int res = 0;
// 滑动窗口的左边界
int start = -1;
for (int i = 0; i < length; i++) {
// 窗口左边界start的位置
if (map.containsKey(cs[i])) {
start = Math.max(start, map.get(cs[i]));
}
// 更新当前字符最后出现的位置
map.put(cs[i], i);
// res获取窗口最大值即为最大长度
res = Math.max(res, i - start);
}
return res;
}
}
复杂度分析
- 时间复杂度:\(O(N)\)
- 空间复杂度:\(O(1)\)
我走得很慢,但我从不后退!

浙公网安备 33010602011771号