LeetCode特训 --- Week2 (主打滑动窗口 + 字符串匹配题目)

目录

滑动窗口原理

真懂了滑动窗口?

滑动 + 字符串细节

开干切题


滑动窗口原理

滑动窗口:维护一前一后两根指针, 或者说一左一右两个指针。更主要的是维护左右指针中的区间. 同时不断的向前滑动,直到整个序列滑动结束,前指针走到序列末尾,所有结果得到收集。

分清职责:

区间维护(题目要求):restriction (限制),限制窗口中的元素。不是什么元素都要维护在窗口中的。必须符合要求,不是所有的水都叫做百岁山。

前指针(探路兵):主打一个铁憨憨,一生的唯一使命就是扩大窗口, 同时维护一个计数器或者是题目要求序列的一个积累判断。 核心:前指针,积累(符合题干的)序列,扩大区间。

后指针(区间头):主打一个收拢区间,收集结果。后指针,按道理应该始终指向(满足题干要求)序列的头部,收集结果。

怎么让后指针(区间的头)走到(符合要求)序列头部?是我们需要考虑的问题。也是一个随意写滑动窗口Bug频出的问题?

如果,兄弟们可以看懂上述理论,那接下来完全有必要可以再瞅瞅,如果看不懂理论,下面还有滑动窗口题目的诸多细节也可以学习。究其根本,滑动窗口也仅仅只是一种思维而已,具体如何实现,各有所好,上述也仅仅只是提供一种思维角度。题目做得慢,bug频出,很多时候是由于各位兄弟蒙没有去自己总结这样的套路。虽然,编程讲究随意性,大佬不管怎么写都没问题,但是人家哪些杀出来的,真不是咱这点小训练可比的,最好就是一周周,勤劳的一个一个小点的攻克,攻克完了总结出自己的切题模板,套路出来

真懂了滑动窗口?

提及滑动窗口, 这仿佛是刷题人必经之路, 人尽皆知的算法了。上面的懂了,是不是刷滑动窗口题目就很顺利了吗?那必然不是,鹅鹅鹅,为啥,上面的模板不是已经挺成型的吗?

NONONO. 滑动窗口题目除了核心整体考滑动窗口,他还可以结合字符串的知识点一起考核。

----- 而不同的字符串匹配规则,对应着不同的处理措施。

滑动 + 字符串细节

字符串基础: 
1. 子串 和 子序列的区别 
    子串: 连续子串              原串: abcdgf  目标串: abc  匹配子串:   abc   
    子序列: 子串中可以多出字符   原串: abcdgf  目标串: acd  匹配子序列: abcd  
2. 子串的排列 和 子序列的区别.   
    排列:包含所要求字符即可, 顺序可变    (应对措施,化变为不变,用位置映射消除顺序之别,map,set,或是vector均可.)    比如 原串:   acbdgh    目标串: [abc]  匹配子序列: acb. (包含acb字符即可)     很明显利用容器 收集路径字符即可。容器自然而然的消除了字符位置之间的顺序性。
    子序列和子串, 顺序都不可变。很明显,指针顺序遍历即可,按照原序列遍历,具备顺序性。


各有各的玩法, 各有各的规则和处理办法.


滑动窗口基础:
1. 指针一前一后, 维护一个区间, 覆盖前进. 前指针一直向后试探, 慢指针根据题目要求维护窗口(区间大小)
2. 窗口滑动的过程中收集结果. 得出ans

开干切题

光说不练,纸上谈兵是无效的。所以刚说的那么多还是需要应用于实际的代码中.

无重复字符的最长子串
力扣

题目要求很明显了,不可以包含重复字符,采取set作为存储窗口区间元素可以,采取数组映射也可以。(维护区间越长越棒棒哒,唯一要求,不可出现重复,出现重复就需要收集结果,收缩区间。)

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int ans = 0;
        unordered_set<int> dict;
        int last = 0, front;
        for (int front = 0; front < s.size(); front++) {
            char ch = s[front];                   //放入窗口的元素.
            while (dict.find(ch) != dict.end()) { //出现重复, 需要收缩区间.
                char delch = s[last];
                dict.erase(delch);
                last ++;
            }  
            dict.insert(ch);
            ans = max(ans, front - last + 1);//获取ans
        }
        return ans;
    }
};


字符串的排列  (无序性 + vector数组映射 + 元素不变性)    排列: 顺序可以打乱. 元素不变性字符不变, 不可多或少  
力扣

字符串的排列,很明显,可以直接利用数组映射来处理。什么意思,这个题目仅仅包含小写字符。所以,可以直接使用26的数组映射就OK了。字符的排列,说白了与顺序无关,仅仅与数目有关系,只要对应字符数目满足了就OK了,还有一点,排列,不能包含其他字符,所以其他字符出现明显,需要upset整个区间

/*
dict: 模板数组,记录着s1串中的信息
count: 记录着滑动窗口滑动过程中的路径信息.
needcnt: 需要满足要求的的字符数目
cnt: 当前满足要求的字符数目
*/

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        std::vector<int> dict(26, 0), count(26, 0);
        int last = 0, front, needcnt = 0, cnt = 0;
        for (auto & e : s1) {
            needcnt += dict[e-'a'] == 0;
            dict[e-'a'] ++;
        }
        for (front = 0; front < s2.size(); front ++) {
            char ch = s2[front];//即将入滑动窗口字符.
            if (!dict[ch-'a']) {//ch不在要求字符中, 卒, 断了, 一切重置.
                count.assign(26, 0);
                last = front + 1;
                cnt = 0;
                continue;
            }
            count[ch-'a'] ++;//放进来.
            if (count[ch-'a'] == dict[ch-'a']) cnt ++;
            if (needcnt == cnt) {//出现ans
                return true;
            }
            while (front - last + 1 >= s1.size()) {//超出区间长度.
                char delch = s2[last];
                cnt -= (count[delch-'a'] == dict[delch-'a']);
                count[delch-'a'] --;
                last ++;
            }
        }
        return false;
    }
};

找到字符串中所有字母异位词 (子序列问题:顺序性 + 元素可变性) 元素可变性, 中间可以插入

力扣

很明显,是上一道题目的一个升级版本. 几乎一毛一样,起码套路是一样滴。而且这个更好写. 收集答案的时候进行收缩就OK了.

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> dict(26, 0), count(26, 0), ans;
        int last = 0, front, cnt = 0, needcnt = 0;
        for (auto &e : p) {
            needcnt += dict[e-'a'] == 0;
            dict[e-'a'] += 1;
        }
        for (front = 0; front < s.size(); front ++) {
            char ch = s[front];
            if (!dict[ch-'a']) {    //序列打断, 全体重置.
                cnt = 0;
                count.assign(26, 0);
                last = front + 1; continue;
            }
            count[ch-'a'] ++;
            cnt += count[ch-'a'] == dict[ch-'a'];
            while (front - last + 1 >= p.size()) {//收缩窗口, 收集ans
                if (cnt == needcnt) ans.push_back(last);
                char delch = s[last];
                cnt -= (count[delch-'a'] == dict[delch-'a']);
                count[delch-'a'] --;
                last ++;
            }
        }
        return ans;
    }
};

通过删除字母匹配到字典里最长单词

力扣

class Solution {
public:
    string findLongestWord(string s, vector<string>& dict) {
        int n = s.size();
        std::sort(dict.begin(), dict.end(), [](string& s1, string& s2) -> bool {
            if (s1.size() > s2.size()) return true;
            if (s1.size() < s2.size()) return false;
            return s1 < s2;
        });
        int last = 0, front = 0;
        for (int i = 0; i < dict.size(); i++) {//遍历所有的dict寻找ans
            for (front = 0; front < s.size(); front++) {
                if (s[front] == dict[i][last]) {
                    last ++;
                }
                if (last >= dict[i].size()) {
                    return dict[i];
                }
            }
            last = 0;//从新开始找下一个。
        }
        return "";
    }
};

思路:话不多说,这个题目,准确来说,不算是滑动窗口。但是也利用了双指针的技巧。先翻译题意,删除字符得到匹配,说白了就是中间存在多余字符,但是顺序性还在,明显的子序列匹配问题。

子序列匹配,待匹配串和匹配串各一个指针同时走,待匹配串的指针走到末尾即为匹配成功。此题要求匹配尽量长,一样长按照字典序,为了尽量少匹配,所以可以先按照规则排序,再依次遍历寻找最优解.

字符串中的所有变位词
力扣

重复了,上面的异位词一样的. 

最小覆盖子串 
典型的滑动窗口,如果搞懂了上述理论直接套用模板解决.
https://leetcode.cn/problems/minimum-window-substring/description/

class Solution {
public:
    string minWindow(string s, string t) {
        int cnt = 0, needcnt = 0, start = 0, minlen = INT_MAX;
        std::vector<int> dict(128), count(128);
        for (auto &e : t) {//记录目标字符
            needcnt += (dict[e] == 0); //需要满足要求的字符数目.     
            dict[e] ++;
        }
        int last = 0, front = 0;
        for (; front < s.size(); front++) {
            char ch = s[front];
            if (dict[ch]) {   //是其中的元素.
                count[ch] ++;//后续多出来的也无所谓.
                cnt += (count[ch] == dict[ch]);//满足字符要求
            }
            while (cnt == needcnt) {//ans
                if ((front - last + 1 < minlen)) {//跟新ans
                    start = last;
                    minlen = front - last + 1; 
                }   
                //尝试缩小窗口
                char delch = s[last];
                last ++;
                if (dict[delch]) {
                    cnt -= dict[delch] == count[delch];
                    count[delch] --;
                }
            }
        }
        return minlen == INT_MAX ? "" : s.substr(start, minlen);
    }
};

重复的DNA序列
https://leetcode.cn/problems/repeated-dna-sequences/

class Solution {
public:
    vector<string> findRepeatedDnaSequences(string s) {
        int last = 0, front;
        vector<string> ans;
        unordered_map<string, int> dict;
        for (front = 0; front < s.size(); front ++) {
            if (front - last + 1 == 10) {
                //maybe ans
                string tmp = s.substr(last, 10);
                if (dict.find(tmp) != dict.end()) {
                    if (dict[tmp] == 1) ans.push_back(tmp);
                }
                dict[tmp] ++;
                last ++;//剔除元素, 缩小窗口
            }
        }
        return ans;
    }
};

最长公共前缀
https://leetcode.cn/problems/longest-common-prefix/

class Solution {
public:
    //遍历走    
    string longestCommonPrefix(vector<string>& strs) {
        char ch;
        int ind = 0;
        while (1) {
            for (int i = 0; i < strs.size(); i++) {
                if (ind >= strs[i].size()) return strs[i];
                ch = (i == 0 ? strs[i][ind] : ch);
                if (ch != strs[i][ind]) {
                    return ind == 0 ? "" : strs[i].substr(0, ind);
                }
            }
            ind ++;
        }

    }
};

滑动窗口的最大值
https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

此题也算是一道经典的复合题目,其中还包含了单调栈或者说单调队列的一点单调序列的思维在里面,这个很经典,如果后续又继续希望可以跟大家分享。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        //维护单调队列吧
        std::deque<int> less_que;//单调递减队列
        vector<int> ans;
        int last = 0, front = 0, need = INT_MIN;//need记录需要值
        for (; front < nums.size(); front ++) {
            while (!less_que.empty() && nums[front] > less_que.back()) {
                less_que.pop_back();
            }
            less_que.push_back(nums[front]);
            if (front - last + 1 == k) {//收集结果, 缩小区间
                ans.push_back(less_que.front());
                if (nums[last] == less_que.front()) {
                    less_que.pop_front();
                }
                last ++;
            }
        }
        return ans;
    }
};

长度最小的子数组
https://leetcode.cn/problems/minimum-size-subarray-sum/?envType=study-plan-v2&id=top-interview-150

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int ans = INT_MAX;
        int sum = 0;
        int last = 0, front = 0;
        for (int front = 0; front < nums.size(); front ++) {
            sum += nums[front];//放入窗口中
            while (sum >= target) {//收集答案并且尝试不停缩小左窗口, 收集最优ans
                ans = min(ans, front - last + 1);
                sum -= nums[last];//last出窗口
                last ++;
            }
        }
        return ans == INT_MAX ? 0 : ans;
    }
};

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小杰312

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值