Leecode Hot100 刷题笔记本-滑动窗口(C++版)

81 阅读3分钟
  1. 76. 最小覆盖子串 困难
  2. 239. 滑动窗口最大值 困难
  3. 438. 找到字符串中所有字母异位词 中等

76. 最小覆盖子串

Screen Shot 2023-08-29 at 9.22.14 AM.png

解法1: 滑动窗口
string minWindow(string s, string t) {
    vector<int> need(128, 0); // 用一个长度为128的数组来表示每个字符的需求情况,初始化为0
    int count = 0; // 用于跟踪字符串t中字符的需求数量
    for (char c : t) {
        need[c]++; // 统计字符串t中每个字符出现的次数
    }
    count = t.length(); // 字符串t的长度表示需要的字符总数
    int l = 0, r = 0, start = 0, size = INT_MAX; // 定义左边界l、右边界r、最小子串的起始位置start和最小子串的长度size,初始化size为INT_MAX表示尚未找到符合要求的子串
    while (r < s.length()) { // 遍历字符串s
        char c = s[r]; // 获取右边界字符
        if (need[c] > 0) {
            count--; // 如果字符c是需要的字符之一,减少需求数量
        }
        need[c]--; // 将字符c加入窗口,减少需求
        if (count == 0) { // 当窗口中已经包含所需的全部字符时
            while (l < r && need[s[l]] < 0) { // 缩减窗口左边界,释放不需要的字符
                need[s[l]]++; // 增加需要的字符的需求
                l++; // 左边界右移
            }
            if (r - l + 1 < size) { // 更新最小子串的信息
                size = r - l + 1; // 更新最小子串的长度
                start = l; // 更新最小子串的起始位置
            }
            need[s[l]]++; // 左边界右移之前需要释放need[s[l]],增加字符s[l]的需求
            l++; // 左边界右移
            count++; // 增加需求数量
        }
        r++; // 右边界右移
    }
    return size == INT_MAX ? "" : s.substr(start, size); // 返回最短子串,如果没有符合要求的子串则返回空字符串
}
  • 时间复杂度: O(n)
  • 空间复杂度:O(n)

239. 滑动窗口最大值

Screen Shot 2023-08-29 at 10.08.29 AM.png

解法1: 优先队列
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    int n = nums.size(); // 获取输入数组的长度
    priority_queue<pair<int, int>> q; // 声明一个优先队列,用于存储数字和对应的索引
    for (int i = 0; i < k; ++i) {
        q.emplace(nums[i], i); // 初始化滑动窗口中的前k个元素,将它们的值和索引加入优先队列
    }
    vector<int> ans = {q.top().first}; // 初始化结果数组,包含第一个窗口的最大值
    for (int i = k; i < n; ++i) {
        q.emplace(nums[i], i); // 将新的元素加入滑动窗口
        while (q.top().second <= i - k) {
            q.pop(); // 如果堆顶元素的索引小于等于当前窗口左边界索引i-k,说明该元素已经不在窗口内,将其从堆中弹出
        }
        ans.push_back(q.top().first); // 将堆顶元素(当前窗口的最大值)加入结果数组
    }
    return ans; // 返回结果数组
}

Screen Shot 2023-08-29 at 5.13.35 PM.png

解法2: 单调队列
  • 初始化双端队列queue, 和结果数组res
  • 数组长度为nums.size() - k + 1
  • 算法的核心思想是使用一个双端队列来维护当前滑动窗口中的元素的索引,确保队列中的元素按照从前到后递减的顺序排列 Screen Shot 2023-08-29 at 5.46.21 PM.png
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    if (nums.size() == 0 || k == 0) return {}; // 如果输入为空数组或k为0,直接返回空数组
    deque<int> deque; // 声明一个双端队列,用于存储元素的索引
    vector<int> res(nums.size() - k + 1); // 创建结果数组,长度为滑动窗口的数量

    for (int j = 0, i = 1 - k; j < nums.size(); i++, j++) {
        // 刚刚形成窗口时,需要从队列的前端删除旧的元素
        // 例如: k = 3, i的初始值为1-3=-2
        // 当i>0的时, 窗口的大小为 4(i = -2, -1, 0, 1), 所以需要出去一个
        if (i > 0 && deque.front() == nums[i - 1]) {
            deque.pop_front();
        }

        // 保持队列递减:从队列的后端删除小于当前元素的元素,确保队列中的元素从前到后递减
        while (!deque.empty() && deque.back() < nums[j]) {
            deque.pop_back();
        }

        // 将当前元素的索引加入队列
        deque.push_back(nums[j]);

        // 记录窗口最大值:在窗口形成后(i >= 0),将队列的前端元素(最大值)加入结果数组
        if (i >= 0) {
            res[i] = deque.front();
        }
    }

    return res; // 返回结果数组,其中包含了每个滑动窗口的最大值
}

Screen Shot 2023-08-29 at 5.46.48 PM.png

438. 找到字符串中所有字母异位词

Screen Shot 2023-08-29 at 5.49.32 PM.png

解法1: 滑动窗口
class Solution {
public:
    vector<int> findAnagrams(string s, string p) 
    {
        
        vector<int> res;
        int sLen = s.size();
        int pLen = p.size();
        //如果子串更长则直接返回
        if(pLen > sLen)
        {
            return vector<int>();
        }
        vector<int> sCount(26);
        vector<int> pCount(26);
        //记录p和s的前几个字符以及数量
        for(int i = 0; i < pLen; i++)
        {
            sCount[s[i] - 'a']++;
            pCount[p[i] - 'a']++;
        }
        if(sCount == pCount)
        {
            res.push_back(0);
        }
        for(int i = pLen; i < sLen; i++)
        {
            // 每次循环添加尾部字符
            sCount[s[i] - 'a']++;
            // 每次循环除去首个字符
            sCount[s[i - pLen] - 'a']--;
            //每次循环判断一次是否相同
            if(sCount == pCount)
            {
                res.push_back(i - pLen + 1);
            }
        }

        return res;
    }
};

Screen Shot 2023-08-29 at 6.15.05 PM.png

解法2: 优化后滑动窗口
  • 不再分别统计滑动窗口和字符串 p 中每种字母的数量,而是统计滑动窗口和字符串 p 中每种字母数量的差;并引入变量 differ 来记录当前窗口与字符串 p 中数量不同的字母的个数,并在滑动窗口的过程中维护它。
  • 在判断滑动窗口中每种字母的数量与字符串 p 中每种字母的数量是否相同时,只需要判断 differ 是否为零即可
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int sLen = s.size(), pLen = p.size();

        if (sLen < pLen) {
            return vector<int>();
        }

        vector<int> ans;
        vector<int> count(26);
        for (int i = 0; i < pLen; ++i) {
            ++count[s[i] - 'a'];
            --count[p[i] - 'a'];
        }

        int differ = 0;
        for (int j = 0; j < 26; ++j) {
            if (count[j] != 0) {
                ++differ;
            }
        }

        if (differ == 0) {
            ans.emplace_back(0);
        }

        for (int i = 0; i < sLen - pLen; ++i) {
            if (count[s[i] - 'a'] == 1) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同
                --differ;
            } else if (count[s[i] - 'a'] == 0) {  // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同
                ++differ;
            }
            --count[s[i] - 'a'];

            if (count[s[i + pLen] - 'a'] == -1) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同
                --differ;
            } else if (count[s[i + pLen] - 'a'] == 0) {  // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同
                ++differ;
            }
            ++count[s[i + pLen] - 'a'];
            
            if (differ == 0) {
                ans.emplace_back(i + 1);
            }
        }

        return ans;
    }
};

Screen Shot 2023-08-29 at 6.22.05 PM.png