3. 无重复字符的最长子串 滑动窗口+哈希表 共三种解法

https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

解法一 基于红黑树(set)的遍历法(自己瞎编的

维护一个set,set中存放着到s[i]为止的最长无重复字符子串。
同时维护stPos,其指向这个子串的起始元素位置。
在遍历过程中,若发现即将加入set的新字符s[i]已经在set中存在,则stPos++,同时在set中移除s[stPos],直到 s[stPos] == s[i] 即找到了那个较早的字符。其他细节详见代码。

代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() == 0) return 0;
        if(s.size() == 1) return 1;
        set<char> st;
        int ans = 1, stPos = 0;
        for(int i = 0; i < s.size(); i++){
            if(st.find(s[i]) == st.end()){
                st.insert(s[i]);
            }
            else{
                while(s[stPos] != s[i]){
                    st.erase(s[stPos]);
                    stPos++;
                }
                if(stPos != i){
                    stPos = stPos + 1;
                }

            }
            ans = max(ans, (int)st.size());
        }
    return ans;
    }
};

image

解法二 滑动窗口(官方题解)

检查了一下官方题解和我的区别,发现实际上官方题解是维护了子数组的end位置,对每一个可能的开头进行遍历。而我的想法是维护子串的开头位置,对每个可能的末尾进行遍历。
官方题解还有一个可以优化的地方(猜测这个地方就是比我慢的原因)官方题解每增加一项就必定对set有擦除和插入操作,但假如要插入的新元素就是上个起始位置的那个元素,其实没有必要对set访问

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        set<char> st;
        int pos = 0;
        int ans = 0;
        for(int i = 0; i < s.size(); i++){
            if(i) st.erase(s[i - 1]);
            while(pos < s.size() && st.find(s[pos]) == st.end()){
                st.insert(s[pos]);
                pos++;
            }
            ans = max(ans, pos - i);
        }
        return ans;
    }
};class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        set<char> st;
        int pos = 0;
        int ans = 0;
        for(int i = 0; i < s.size(); i++){
            if(i) st.erase(s[i - 1]);
            while(pos < s.size() && st.find(s[pos]) == st.end()){
                st.insert(s[pos]);
                pos++;
            }
            ans = max(ans, pos - i);
        }
        return ans;
    }
};

image

解法三 记录每个字母的下标(哈希)

这是评论区找到的牛逼方法,速度是98%。
维护一个last数组,其中记录每个字母出现的最新位置,之后遍历字符串。维护start变量作为子串的起始长度,则对每一个新加进数组来的字符,都需要更新start为其上一次出现的位置加一。
具体实现见代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> last(128, -1);
        int ans = 0, start = 0;
        for(int i = 0; i < s.size(); i++){
            start = max(start, last[s[i]] + 1);
            last[s[i]] = i;
            ans = max(ans, i - start + 1);
        }

        return ans;
    }
};

image

posted @ 2021-11-06 18:12  柚子z  阅读(81)  评论(0)    收藏  举报