题目
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
思路
又是一个字符串的题,这里就需要吸取上次的教训了。空字符…算了,是0,按常规流程走也直接是整型的初始值了。
因为需要找的是无重复字符的子串的最大长度,因此区别于子序列,字符之间必须是连续的才行。
我们首先以程序的方式思考一下。
找子串,就需要对字符串进行一个个字符的遍历,需要从第一个字符开始计算子串并同步当前找到的子串的长度。
由于找的是最大长度,因此只要没有遇到重复的字符,就可以一直加下去。但是如果遇到重复的字符怎么办?
重复的字符是没办法遇见其位置的,可能是开头,也可能是结尾,也可能是中间任意一个位置。比如ababcd,遍历到第一个ab,然后会遇到第二个a,这个的a在开头;abcddce则在dd遇到重复的d,而这个d在abcd的结尾;abcdebec则是在ebe这里首先遇到重复的字符b,它在abcde的中间位置,是整个字符串的第二个位置。
那么如何解决这个办法呢?因为遇到重复就表示当前子串已达到了目前子串的最大值,因此先行将此长度与目前最长的子串长度作比较,更新最长的长度数值。然后,由于子串不能包含重复的字符,因此下一个子串要从最近的无重复字符开始,也就是从被重复的字符位置开始。举个例子:abcdebec这个字符串,ebe这里首先重复了b,那么在更新了最长长度后,下一个子串的计算就应该从被重复的b开始,也就是abcde变更为cde,然后再加上重复了的b,成为新子串:cdeb。这个时候,新的无重复字符子串长度是4。接下来遇到e,又是一个重复,那么现在维护的子串就应该从cdeb变成b,再加上e,也就是be。
也就是说,在遍历的时候,需要维护一个子串变量和最长长度变量。
每一次循环都要检查这个字符是否重复了,如果重复了,就按照上边的方法处理;如果没有重复,就加入到维护的子串变量中并更新最长长度变量。
代码
C#代码:
public class Solution {
public int LengthOfLongestSubstring(string s) {
var length = 0;
var t = "";
for (int i = 0, t_length = 0; i < s.Length; i++)
{
if (t.Contains(s[i]))
{
if (length < t_length)
{
length = t_length;
}
t += s[i];
t = t.Substring(t.IndexOf(s[i])+1);
t_length = t.Length;
}
else
{
t += s[i];
t_length++;
if (i + 1 == s.Length && length < t_length)
{
length = t_length;
}
}
}
return length;
}
}
官方解法
滑动窗口法
基础思路的图片介绍:
检查字符串是否包含某字符(即重复字符):
可以看到的是,官方使用了双指针+哈希的方法来一遍移动指针,一边计算非重复子串长度。
每一次移动右指针,都要先检测当前子串是否包含了右指针对应的字符(使用哈希表),如果包含了(也就是重复了),就需要移动左指针来去除重复,直到不重复为止才能继续移动右指针。
官方代码
java代码:
class Solution {
public int lengthOfLongestSubstring(String s) {
// 哈希集合,记录每个字符是否出现过
Set<Character> occ = new HashSet<Character>();
int n = s.length();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = -1, ans = 0;
for (int i = 0; i < n; ++i) {
if (i != 0) {
// 左指针向右移动一格,移除一个字符
occ.remove(s.charAt(i - 1));
}
while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
// 不断地移动右指针
occ.add(s.charAt(rk + 1));
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
ans = Math.max(ans, rk - i + 1);
}
return ans;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetc-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
滑动窗口法的解释
什么是滑动窗口?
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
时间复杂度:O(n)O(n)
作者:powcai
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-dong-chuang-kou-by-powcai/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
感想
前边也做了字符串的题,这里也是个字符串的题。由于字符串其实是连续的字符数组,因此见到的解法都是使用指针完成的。指针的执行效率当然是更高的,之后再遇到字符串题,我也会尝试用指针解决一下问题的。涉及到字符重复的检测,也就是字符串中是否包含某个字符,应当直接想到唯一性的数据结构,比如说一系列的键值对类型(必须保持键的唯一性)。