滑动窗口

滑动窗口题目整理

通过 LeetCode 上的 5 道简单的题总结如下:
无论是窗口可变还是窗口恒定,用两个指针 left 和 right 来维护一个窗口,要考虑的是窗口何时满足条件,要处理的就是元素进窗口以及元素离开窗口这两个状态。

无重复字符的最长子串

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

满足条件的状态: 窗口中的字符没有重复
不满足条件的状态: 窗口中的字符有重复,此时需要移动 left 指针来使窗口重新满足条件。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int front = 0, right = 0;
        int len = s.length();
        int ans = 0;
        int a[1000]={0};
        while (right < len) {
            a[s[right]]++;  
            if (a[s[right]] == 1) {
                ans = max(ans, right - front + 1);
            }else if (a[s[right]] == 2) {
                while (s[right] != s[front]) {
                    a[s[front]]--;
                    front++;
                }
                a[s[front]]--;
                front++;
            }
            right++;
        }
        return ans;
    }
};

串联所有单词的子串

题意: 给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。(注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。)
题解: 由单词的长度相同出发,可以将 s 按照单词的长度切割成若干个单词序列。从 s 的第一个单词开始切割与从 s 的第二个单词开始切割,所得到的单词序列不同。为了得到所有的情况,需要从 s 的前 words 长度出发都切割一遍。
满足条件的状态: 窗口中恰好包含所有单词 words。
不满足条件的状态: 情况1:窗口中某个单词多了,移动 left 指针,使该单词个数满足条件
情况2:窗口中出现了其他单词,删除当前窗口中所有单词,使窗口大小为 0,重新开始匹配

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string, int> wordhash;
        int num = words.size();
        for (int i = 0; i < num; ++i) {
            wordhash[words[i]]++;
        }
        int len = words[0].length();
        int windowlen = len * num;
        int n = s.length();
        vector<int> ans;
        for (int i = 0; i < len; ++i) {
            unordered_map<string, int> nowhash;
            int left = i, right = i;
            while (n - right >= len) {
                string temp = s.substr(right, len);
                right += len;
                //新单词不合法时
                if (wordhash.find(temp) == wordhash.end()) {
                    left = right;
                    nowhash.clear();
                }else if (nowhash[temp] == wordhash[temp]) {
                    while (left < right) {
                        string t = s.substr(left, len);
                        left += len;
                        if (t == temp) {
                            break;
                        }
                        nowhash[t]--;
                    }
                }else {//合法
                    nowhash[temp]++;
                }
                if (right - left == windowlen) {//判断是否单词完全匹配
                    ans.push_back(left);
                }
            }
        }
        return ans;
    }
};

最小覆盖子串

题意: 给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。
满足条件的状态: 窗口中至少包含 T 的所有字符。此时,尽可能移动 left 指针,使得字符串长度尽可能小。

class Solution {
public:
    bool check(unordered_map<char, int> &a, unordered_map<char, int> &b) {
        unordered_map<char, int>::iterator it;
        for (it = a.begin(); it != a.end(); ++it) {
            if (a[it->first] > b[it->first])
                return false;
        }
        return true;
    }

    string minWindow(string s, string t) {
        unordered_map<char, int> st;
        int len = t.length();
        for (int i = 0; i < len; ++i) {
            st[t[i]]++;
        }
        int n = s.length();
        int left = 0, right = 0;
        int ans = n+1, pos = 0;
        unordered_map<char, int> nows;
        while (right < n) {
            char temp = s[right];
            right++;
            nows[temp]++;
            while (check(st, nows) && left < right) {//满足包含所有这一条件时
                if (right - left < ans) {
                    ans = right - left;
                    pos = left;
                }
                nows[s[left]]--;
                left++;
            }
        } 
        if (ans == n+1) ans = 0;
        return s.substr(pos, ans);
    }
};

字符串的排列

题意: 给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的子串。
题解: 这题其实是第二题的简单版。

class Solution {
public:
    bool check(unordered_map<char, int> &a, unordered_map<char, int> &b) {
        unordered_map<char, int>::iterator it;
        for (it = a.begin(); it != a.end(); ++it) {
            if (it->second != b[it->first])
                return false;
        }
        return true;
    }
    bool checkInclusion(string s1, string s2) {
        int len = s1.length(), n = s2.length();
        unordered_map<char, int> cmp;
        unordered_map<char, int> now;
        for (int i = 0; i < len; ++i) {
            cmp[s1[i]]++;
        }
        int left = 0, right = 0;
        while (right < n) {
            char t = s2[right];
            right++;
            now[t]++;
            if (right - left == len) {
                if (check(cmp, now)) {
                    return true;
                }
                now[s2[left]]--;
                left++;
            }
        }
        return false;
    }
};

滑动窗口的最大值

题意: 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
题解: 这个题与上面几个题不同之处在于滑动窗口大小固定,需要一个贪心的思想,即每次进窗口的值需要把之前的比它小的数全部去除,因为它们将不会再是最大值了。需要用一个双端队列来维护这个最大值。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans;
        deque<int> wd;
        int n = nums.size();
        for (int i = 0; i < k; ++i) {
            while (!wd.empty() && nums[i] > nums[wd.back()]) {
                wd.pop_back();
            }
            wd.push_back(i);
        }
        ans.push_back(nums[wd.front()]);
        for (int i = k; i < n; ++i) {
            if (i - wd.front() >= k) {
                wd.pop_front();
            }
            while (!wd.empty() && nums[i] > nums[wd.back()]) {
                wd.pop_back();
            }
            wd.push_back(i);
            ans.push_back(nums[wd.front()]);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值