滑动窗口算法是双指针的一种应用,适用于解决离散结构中具有“连续”性质的最优化问题

以下所有例题均来源于力扣LeetCode

例题一


3. 无重复字符的最长子串

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

示例 1:

输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

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

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
	//注意判空
        if(s.size()==0) return 0;
	//无需有序
        unordered_set<char> temp;
        int left = 0, ans = 0;
        for(int i = 0; i < s.size(); i++)
        {
	//消除重复字符
            while(temp.find(s[i]) != temp.end())
            {
                temp.erase(s[left++]);
            }
            ans = max(ans, i - left + 1);
            temp.insert(s[i]);
        }
        return ans;
    }
};

例题二

  1. 长度最小的子数组
    给定一个含有 n 个正整数的数组和一个正整数 target 。
    找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
代码如下

class Solution {
public:
   int minSubArrayLen(int s, vector<int>& nums) {
       int n = nums.size();
       if (n == 0) {
           return 0;
       }
       //初始化为无穷大
       int ans = INT_MAX;
       int start = 0, end = 0;
       int sum = 0;
       while (end < n) {
           sum += nums[end];
   	   //去除重复
           while (sum >= s) {
               ans = min(ans, end - start + 1);
               sum -= nums[start];
               start++;
           }
           end++;
       }
       return ans == INT_MAX ? 0 : ans;
   }
};

例题三

  1. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。
示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

思路:我们在 s 上滑动窗口,通过移动right指针不断扩张窗口。当窗口包含 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,如果这个动态表中包含 t 的哈希表中的所有字符,并且对应的个数都不小于t 的哈希表中各个字符的个数,那么当前的窗口是满足条件的。
代码如下:

class Solution {
public:
    //哈希表
    unordered_map <char, int> t_map, s_map;

    //判断t中的字符是否已经全部包含于窗口中
    bool check() 
    {
        for (const auto &p: t_map) 
        {
            if (s_map[p.first] < p.second) 
                return false;
        }
        return true;
    }

    string minWindow(string s, string t) 
    {
        if(s.size() < t.size() || s.size() == 0 || t.size() == 0)
            return string();
        
        for (const auto &ch: t)
            t_map[ch]++;

        //左指针与右指针
        int l = 0, r = 0;
        int len = INT_MAX, ansL = -1;
        //无符号数与有符号数,首先会将有符号数转化为无符号数
        //size()获得的是一个无符号数,必须先行转换,否则后续判断会错误
        int s_len = s.size();
        while (r < s_len) 
        {
            if (t_map.find(s[r]) != t_map.end())
            {
                s_map[s[r]]++;
            }
            //尝试缩小窗口
            while (check() && l <= r) 
            {
                if (r - l + 1 < len) 
                {
                    len = r - l + 1;
                    ansL = l;
                }
                if (t_map.find(s[l]) != t_map.end())
                {
                    s_map[s[l]]--;
                } 
                ++l;
            }
            r++;
        }
        return ansL == -1 ? string() : s.substr(ansL, len);
    }
};

例题四

滑动窗口的最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回 滑动窗口中的最大值 。

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
示例 2:

输入:nums = [1], k = 1
输出:[1]

题解一

对于求最大值,联想到的数据结构就是大顶堆,在C++中可以用优先队列来实现。初始时,我们将数组的前 k 个元素放入优先队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。通过不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),即元素num在数组中的下标为index

class Solution {
public:
    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);
        }
        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();
            }
            ans.push_back(q.top().first);
        }
        return ans;
    }
};

时间复杂度为\(O(nlogn)\),空间复杂度为\(O(n)\)

posted on 2023-02-04 01:11  sc01  阅读(61)  评论(0编辑  收藏  举报