力扣刷题总结之滑动窗口思想

滑动窗口算法通用思想:

什么是滑动窗口?

其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!

如何移动?

我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!

滑动窗口算法的思路是这样:
  1. 我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。
  2. 我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。
  3. 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。
  4. 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

力扣滑动窗口类型集合

1、无重复字符的最长子串 --中等

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

示例 1:

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


示例 2:

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

示例 3:

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

解答

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() == 0) return 0;
        unordered_map<char,int> hash(0);
        int maxStr = 0;
        int left = 0;
        for(int i = 0; i < s.size(); i++){
            hash[s[i]] ++;
            while (hash[s[i]] > 1){
                hash[s[left]]--;
                left ++;
            }
            maxStr = max(maxStr,i-left+1);
    }
       return maxStr;
    }
};

2、长度最小的子数组 --中等

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

示例:

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

进阶:

如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        if(nums.empty())
            return 0;
        int left  = 0;
        int right = 0;
        int minLen = INT_MAX;
        int val = nums[0];
        while(right <= nums.size() - 1)
        {
            if(val >= s)
                 {
                    minLen = min(minLen,right-left+1);
                    val = val -nums[left];
                    left++;
                }
            else
            {
                right++;
                if(right < nums.size())
                    val = val + nums[right];
            }
        }
        return minLen == INT_MAX ? 0 : minLen; 
    }
};

3、最小覆盖子串 --困难

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"

说明:

如果 S 中不存这样的子串,则返回空字符串 “”。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

class Solution {
public:
    string minWindow(string s, string t) {
        //记录所找出的字串的起始位子和长度
        int start = 0,minlen = INT_MAX;
        //设置双指针
        int left = 0, right = 0;
        //使用哈希来判断是否为子串
        unordered_map<char,int> window;
        unordered_map<char,int> needs;
        for(auto s : t)
        {
            needs[s]++;
        }
        //匹配计数
        int match = 0;

        while(right < s.size())
        {
            char c = s[right];
            if(needs.count(c))  //存在c,返回1,否则,返回0
            {
                window[c]++;
                if(window[c] == needs[c])
                {
                    match++;
                }
            }
            right++;

            while(match == needs.size())
            {
                if(right - left < minlen)
                {
                    //更新最小字串的起始位置和长度
                    start = left;
                    minlen = right - left;
                }
                char c1 = s[left];
                if(needs.count(c1))
                {
                    window[c1]--;
                    if(window[c1] < needs[c1])
                    {
                        match--;
                    }
                }
                left++;
            }
        }
        return minlen == INT_MAX ? "" : s.substr(start,minlen);
    }
};

4、串联所有单词的子串 --困难

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1:

输入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例 2:

输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {

        vector<int> result;
        if(!words.size())//第一次提交报错,说是vector错了,我以为是return的时候错了,没想到是因为没有判断words这个vector是否为空
            return result;

        int sSize = s.size();
        int wSize = words[0].size();
        int wNum = words.size();
        map<string, int> wordHash;
        map<string, int> subHash;
        
        for(auto it:words)
            wordHash[it]++;

        for(int i=0;i<=sSize - wSize*wNum;i++){//这里每次滑动一个字母
            string subStr = s.substr(i,wSize*wNum);
            for(int j=0;j<=wSize*wNum - wSize;j+=wSize){//这里每次滑动一个单词
                string tmpWord = subStr.substr(j,wSize);
                if(wordHash[tmpWord]==0)//如果tmpWord这个key不存在,则这种访问方式其实是在wordHash里直接创建了一个key为tmpword,value为int的默认值0的pair。在这道题中由于最初的wordHash中不会存在0这个值,所以就可以通过判断key的value是否为0来确定key是否存在于原来的wordHash中。
                    break;//如果该单词不存在则直接跳出循环
                subHash[tmpWord]++;
                if(subHash[tmpWord]>wordHash[tmpWord])
                    break;//如果该单词的出现次数多了,也调出循环。因为单词出现的次数必须完全等于words中的次数
                if(j == wSize*wNum - wSize)
                    result.push_back(i);//如果循环到末尾了,说明这个子序列满足要求(如果到达最后一个,则成功)
            }
            subHash.clear();
        }
        return result;
    }
};

5、滑动窗口最大值 --困难

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

返回滑动窗口中的最大值。

进阶:

你能在线性时间复杂度内解决此题吗?

示例:

输入: 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


提示:

1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4
1 <= k <= nums.length
class Solution {
public:
	vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(k==0)return {};
		vector<int>res;
		deque<size_t>window;
		/*Init K integers in the list*/
		for (size_t i = 0; i < k; i++) {
			while (!window.empty()  && nums[i] > nums[window.back()]) {
				window.pop_back();
			}
			window.push_back(i);
		}
		res.push_back(nums[window.front()]);
		/*End of initialization*/
		for (size_t i = k; i < nums.size(); i++) {
			if (!window.empty() && window.front() <= i - k) {
				window.pop_front();
			}
			while (!window.empty() && nums[i] > nums[window.back()]) {
				window.pop_back();
			}
			window.push_back(i);
			res.push_back(nums[window.front()]);
		}
		return res;
	}
};

6、字符串的排列 --中等

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,第一个字符串的排列之一是第二个字符串的子串。

示例1:

输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").

示例2:

输入: s1= "ab" s2 = "eidboaoo"
输出: False
class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int len1 = s1.size();
        int len2 = s2.size();
        if(len1 > len2)
            return false;
          vector<int> needs(26,0),window(26,0);//needs表示s1的字符表,window表示窗口,用来匹配s1的字符表needs
        int left = 0, right = 0;
        for(auto c : s1)
        {
            needs[c- 'a'] ++;
            window[s2[right++] - 'a'] ++;
        }
        if(needs == window) 
            return true;
        while(right<s2.size())//开始滑动窗口
        {
            window[s2[right++]-'a']++;
            window[s2[left++]-'a']--;
            if(window==needs)return true;
        }
        return false;
    }
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值