LeetCode 无重复字符的最长子串
@author:Jingdai
@date:2022.01.17
题目描述
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。示例输入
s = "abcabcbb"
示例输出
3
解释
因为无重复字符的最长子串是 "abc",所以其长度为 3。
思路及代码
我们可以从头到尾遍历每个字符,记录以每个字符结尾的不含重复字符的长度,然后从中选出最长的就是结果。那现在问题就变成了怎么求出以每个字符结尾的不含重复字符的长度。
方法1:基于滑动窗口
方法1利用滑动窗口,滑动窗口里的内容就是不含重复字符的子串。滑动窗口的右边界很容易,就是我们遍历字符串的下标。而关键就是求出滑动窗口的左边界 left,它分为几种情况,下面分别进行讨论。
当遍历的字符之前没有出现过时:
abcd
如上,当遍历到第4个字符 d 时,前面没有出现过字符 d(下标为3),left 一直是 0,这时左边界应该保持不变,还是0。所以此时滑动窗口的长度应该为(3-0+1=4)。
当遍历的字符之前出现过,且当前字符在前一个滑动窗口中时,需要改变 left 的值:
abcdb
如上,当遍历到第二个b时,前面出现过一个b,此时的left为0,显然left应该更新,更新为原来的最近的b的下标加一,即c的下标2,所以此时滑动窗口的长度为(4-2+1=3)。
当遍历的字符之前出现过,且当前字符不在前一个滑动窗口中时,left 的值保持不变:
babba
如上,当遍历到最后一个a时,此时left为第3个b的下标3,显然此时left应该保持不变。
综上,为了让两种情况结合,当遍历的字符之前出现过时,left应该为left和最近出现过的下标加一的最大值。
根据此,就可以写出代码,代码如下:
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0 || s.length() == 1) {
return s.length();
}
int len = s.length();
char[] chars = s.toCharArray();
Map<Character, Integer> memo = new HashMap<>();
int maxLen = 1;
int left = 0;
memo.put(chars[0], 0);
for (int i = 1; i < len; i++) {
if (memo.containsKey(chars[i])) {
left = Math.max(left, memo.get(chars[i]) + 1);
}
memo.put(chars[i], i);
maxLen = Math.max(maxLen, i - left + 1);
}
return maxLen;
}
方法2:动态规划
首先看状态的定义,这里我们定义为每个下标结尾的无重复子串的长度,下面就是如何利用前一个状态推出下一个状态了。长度受到两个限制:1,前一个长度加1。2,前一个一样的字符的中间的长度。这两个限制取较小值就是答案。下面进行证明。
当遍历的字符之前没有出现过时:
abcd
如上,当遍历到第4个字符 d 时,前面没有出现过字符 d(下标为3),此时dp[3]就是dp[2]+1。
当遍历的字符之前出现过,且当前字符在前一个滑动窗口中时:
abcdb
如上,当遍历到第二个b时,前面出现过一个b,此时长度就是前一个一样的字符中间的长度,即 cdb 的长度3。
当遍历的字符之前出现过,且当前字符不在前一个滑动窗口中时:
babba
如上,当遍历到最后一个a时,长度dp[4] = dp[3] + 1 = 2。
综上,为了使上面所有情况融合,dp[i] = Math.min(dp[i-1] + 1, 前一个一样的字符中间的长度)。
最后,为了统一,如果前面没有出现过,我们假设前面出现过,其下标为-1,这样就可以不用再多一个判断了。最后代码如下。
public int lengthOfLongestSubstring(String s) {
if (s.length() == 0 || s.length() == 1) {
return s.length();
}
char[] chars = s.toCharArray();
int len = chars.length;
// if there is no such key in map, its -1
Map<Character, Integer> memo = new HashMap<>();
int[] dp = new int[len];
dp[0] = 1;
memo.put(chars[0], 0);
int maxLen = 1;
for (int i = 1; i < len; i++) {
dp[i] = Math.min(dp[i-1] + 1, i - memo.getOrDefault(chars[i], -1));
memo.put(chars[i], i);
maxLen = Math.max(dp[i], maxLen);
}
return maxLen;
}