「Day3」一文看懂滑动窗口「hot 100 之leetcode3」

129 阅读6分钟

一、题目

3. 无重复字符的最长子串。难度:中等。

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。

更多细节请参看官方题目详情

二、题解

2.1 思路分析

从左向右遍历字符串的每一位,找不含重复元素的最长子串。

拿题目中的s = "pwwkew"举例说明

(1)start=0,end=0

pwwkew
012345
start
end

维护两个变量 start 和 end ,他们可以在每个字符上滑动,在滑动的过程中,我们去找最长的无重复元素的子串。他们的初始位置都在第0个元素上,这里是p,此时不含有重复字符的 最长子串 为 p, 其长度为1【思考代码层面:ans=end-start+1】。

(2)end++;start=0,end=1

pwwkew
012345
start
end

将 end 向后移动,可以知道 p、w都只出现了一次,且连续, 此时不含有重复字符的 最长子串 为 pw, 其长度为2【思考代码层面:ans=end-start+1】。

(3)end++;start=0,end=2

pwwkew
012345
start
end

继续将 end 向后移动,判断新的字符是否重复出现,可以知道新的(也就是end)位置上的元素 w 重复出现了,与上一个字符重复,所以此时最新的子串应该从当前位置开始重新计算,更新start,新的start的位置应该是重复位的下一位,同时保存历史最长子串的长度2,与未来的最长子串的长度比较选取较大的值为最终的结果。

更新如下

pwwkew
012345
start
end

当前位置不含有重复字符的 最长子串 为 pw, 其长度为2。继续向后遍历。

(4)end++;start=2,end=3

pwwkew
012345
start
end

继续将 end 向后移动,判断新的字符是否重复出现,此时不含有重复字符的 最长子串 为原来的 pw 或者现在的wk, 其长度均为2【思考代码层面:ans = max{ans,end-start+1}】。

(5)end++;start=2,end=4

pwwkew
012345
start
end

继续将 end 向后移动,判断新的字符是否重复出现,此时不含有重复字符的 最长子串 为 wke, 其长度为3【思考代码层面:ans = max{ans,end-start+1}】。

(6)end++;start=2,end=5

pwwkew
012345
start
end

继续将 end 向后移动,判断新的字符是否重复出现,可以知道新的(也就是end)位置上的元素 w 重复出现了,与第2个位置处的字符重复,更新start,新的start的位置应该是重复位的下一位,所以需要更新start为3。

更新如下

pwwkew
012345
start
end

(7)end++;start=2,end=6

end后移,超出字符串最大长度,遍历结束,返回最长字串长度3

2.2 代码思路分析及优化

以上就是这道题的总体思路了,接下来我们就要思考代码层面,如何达到效率最高?

值得我们重点考虑的就是,如何判断新的位置的字符是否重复出现?和什么位置重复?进而准确更新start。一般是否重复的问题我们会想到map、hashmap;那么start的位置呢?由上面的分析我们可以知道,start的位置取决于重复元素上一次出现的位置,然后再+1,所以我们应该记录重复元素上一次出现的位置,所以我们可以用 hashmap 的 value 来记录每个字符上一次出现的位置,这里起名为 characterPosMap。start和end及时更新即可。

ans=0; (1)start=0,end = 0;ans = max{ans,end- start + 1 = 1} =1;更新map如下所示

keyp
value0

(2)end++,start=0,end = 1;ans = max{ans,end- start + 1 = 2}=2;更新map如下所示

keypw
value01

(3)end++,start=0,end = 2;w 重复,更新 start = 重复的位置+1=characterPosMap[w]+1=1+1=2;更新map如下所示

keypw
value02

(4)end++,start=2,end = 3;ans = max{ans,end- start + 1 = 2}=2;更新map如下所示

keypwk
value023

(5)end++,start=2,end = 4;ans = max{ans,end- start + 1 = 3}=3;更新map如下所示

keypwke
value0234

(6)end++,start=2,end = 5; 新的w是重复的,start = 重复的位置+1=characterPosMap[w]+1=2+1=3;更新w的位置为新的位置5,具体更新map如下所示

keypwke
value0534

(7)end++,end=6,结束,return ans = 3;

2.3 C++ 相关语法

2.3.1 哈希表

前面已经介绍过了,可以自行阅读学习

2.3.2 字符串 string 的元素访问

使用字符串类的成员函数 operator[]at 来访问 C++ 中的字符串对象中的每个字符。

(1)operator[]

operator[] 可以像访问数组元素一样,使用下标来访问字符串中的字符,如下所示:

cppCopy code
std::string str = "hello";
for (int i = 0; i < str.size(); i++) {
    char ch = str[i];
    // 使用 ch 进行操作
}

(2)at

at 函数与 operator[] 函数类似,但会执行边界检查,以确保不会越界访问字符串。下面是使用 at 的示例:

cppCopy code
std::string str = "hello";
for (int i = 0; i < str.size(); i++) {
    char ch = str.at(i);
    // 使用 ch 进行操作
}

(3) count

我们经常使用 count(key) > 0 的方式来判断哈希表中是否有当前的元素,true就是存在,false就是不存在。

2.4 题目代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> characterPosMap;
        int start = 0, end = 0;
        int ans = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (characterPosMap.count(s.at(i)) > 0)
            {
                start = characterPosMap[s.at(i)] + 1;
                end++;
                characterPosMap[s.at(i)] = i;
            }
            else
            {
                characterPosMap.insert(make_pair(s.at(i),i));
                ans = max(ans, end - start + 1);
                end++;
                //characterPosMap.emplace(s.at(i), i);
            }
        }
        return ans;
    }
};

但这段代码,对于例子 "tmmzuxt" 是有问题的,我们的代码的结果是4,预期是5。原因是对于最后一个字符t在历史中是出现过的,但是并不在当前字符串中所以当前字符串的长度还可以再增加1,所以对于if (characterPosMap.count(s.at(i)) > 0)这个的分支,我们还应该判断当前字符是否存在于[begin,end]中,重复字符不在这个中,是需要更新 ans 的长度的。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> characterPosMap;
        int start = 0, end = 0;
        int ans = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (characterPosMap.count(s.at(i)) > 0)
            {
                
                if(characterPosMap[s.at(i)] < start)
                {
                    ans = max(ans, end - start + 1);
                    end++;
                    characterPosMap[s.at(i)] = i;
                }
                else
                {
                    start = characterPosMap[s.at(i)] + 1;
                    end++;
                    characterPosMap[s.at(i)] = i;
                }
                
            }
            else
            {
                characterPosMap.insert(make_pair(s.at(i),i));
                //characterPosMap.emplace(s.at(i), i);
                ans = max(ans, end - start + 1);
                end++;
                
            }
        }
        return ans;
    }
};

使用 characterPosMap.insert(make_pair(s.at(i),i));

image.png

使用 characterPosMap.emplace(s.at(i), i);

image.png

总体相差不大,可忽略。

三、总结

题目知识点进度
136数组、位运算(异或)、哈希表1
121数组、动态规划2
3字符串、滑动窗口、哈希表3