滑动窗口大法---刷题总结

Leecode题目
219. 存在重复元素 II

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

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

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

最小不超过k,相当于维护一个大小为K的容器,使它的元素不能重复,当然如果能重复那就说明有答案咯!

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        
        unordered_set<int> us;
        
        for(int i = 0;i < nums.size();i++){
          if(us.find(nums[i]) != us.end()) return true;
          
          us.insert(nums[i]);
          if(us.size() > k) us.erase(us.find(nums[i-k]));
        }

        return false;
    }
};

220. 存在重复元素 III

给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。

示例 1:

输入: nums = [1,2,3,1], k = 3, t = 0
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1, t = 2
输出: true

示例 3:

输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

思路,与上一题的不同,在于,查找的值是一个范围,可以用二分查找法upper_bound()(大于等于的第一个值),注意你找的起始点应该是nums[i]-t ,这点很机智,实际上很多绝对值都是一个范围,满足范围里的数都可以!!!也就是说你满足这个范围的最小值,就是你查找的结果,只要它小于最大的范围值nums[i]+t 就可以了!!!

class Solution {
public:
    bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
        // 活动窗口
        set<int> record;
        for(int i=0; i<nums.size(); i++){

            auto s = record.lower_bound((double)nums[i]-t); // 防溢出

            if(s != record.end() && *s <= (double)nums[i]+t)
                return true;

            record.insert(nums[i]);
            // record.size保持<=k+1
            // 当record达到k+1时, 马上要删去一个, 为下一个元素腾出空间
            if(record.size() == k+1)
                record.erase(nums[i-k]);
        }
        return false;
    }
};

239. 滑动窗口最大值

给定一个数组 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

这次是真的滑动窗口了,实际上这题的方法却不是用的滑动窗口…

方法一:我想到的第一个方法是用multiset,维护一个大小为k的容器,然后不断取最大值。
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {

        vector<int> result;
        if(nums.size() == 0) return result;
        multiset<int> ms(nums.begin(),nums.begin()+k);

        result.push_back(*(ms.rbegin()));
        for(int i = k;i < nums.size();i++){

            ms.erase(ms.find(nums[i-k]));
            ms.insert(nums[i]);
            result.push_back(*(ms.rbegin()));
        }

        return result;
    }
};

方法二:动态规划

我也不知道这算不算动态规划,

  1. 把所有数组划分成k大小的块
  2. 求每个块内从左边开始当前的最大值
  3. 求每个块右边开始当前的最大值

我说肯定说不清楚上图比较清楚
来自Leecode
最后结果就是,如果滑动窗口在一个块里面,就不用说了。
如果滑动窗口在两个块里面,如下图,那么结果就是left[2]和right[4]比较!

在这里插入图片描述

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        if(nums.size() == 0) return result;
        vector<int> left(nums.size(),0);
        vector<int> right(nums.size(),0);

        int nsize = nums.size();
        for(int i = 0;i < nums.size();i += k){
            int d = min(k,nsize);
            int maxleft = nums[i];
            int maxright = nums[i+d-1];

            for(int j = 0;j < d;j++){
               maxleft = max(maxleft,nums[i+j]);
               maxright = max(maxright,nums[i+d-1-j]);
               left[i+j] = maxleft;
               right[i+d-1-j] = maxright;
            }
            nsize = nsize-k;
        }

        for(int i = k-1;i < nums.size();i++){
            result.push_back(max(right[i-k+1],left[i]));
        }

        return result;
    }
};
方法三:双端队列

参考键值offer,为维护一个滑动窗口的最大值,用双端队列保存,每加入一个值n,把之前小于当前n的值都出队。
如:
【352】461,k = 3
此时deque里面的值为52(因为在添加5的时候3就淘汰了),当加入4的时候,2就要先出队!然后还要维护一个下标队列,当最大值要出队的时候,就要重新更新最大值。代码就不上了。

424. 替换后的最长重复字符

给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 k 次。在执行上述操作后,找到包含重复字母的最长子串的长度。

示例 1:

输入:
s = “ABAB”, k = 2
输出:
4
解释:
用两个’A’替换为两个’B’,反之亦然。

示例 2:

输入:
s = “AABABBA”, k = 1
输出:
4
解释:
将中间的一个’A’替换为’B’,字符串变为 “AABBBBA”。
子串 “BBBB” 有最长重复字母, 答案为 4。

错误思维:
这题我当时没有头绪,后来想到的是用滑动窗口。

  1. 用i,j两个点来维护滑动窗口左边右边,然后从头开始遍历,以s[i]为字符的子队列
  2. j向后滑动,若是与s[i]相同,加入,若是不同,如果k>0同样可以加入,直达不满足上述条件。
  3. 当不满足上述条件时,i要开始滑动,滑动的不是下一个节点,而是不等于当前节点值的下一个节点,如AABA,i当前为0,下一个滑动为2,之后重复上述过程。

结果后来出现了报错:

当BAAA,k=1时,我的结果是3,实际上应该是4.

错误原因在于:
不应该以i的位置为滑动窗口子串的重复字符,要不断更新滑动窗口中滑动窗口子串的最大重复字符。

而且总结一下滑动窗口的经验,其实每个滑动窗口都应该有一个集合用来记录滑动窗口的状态。这题就应该记录的是每个字符在子串出现的次数,用这个记录,来更新最大重复字符!

正确解法:
首先明白这题子串满足的条件是:

当前子串出现重复字符最大值+k > 当前子串的长度

那么就是满足的。于是我们可以开始维护一个滑动窗口wd,起始为i ,结尾为j。

  1. 从头开始遍历,添加一个s[j+1]进去,若是满足上述条件,添加成功,若是不满足,则滑动窗口向右滑动。
  2. 且在添加或者滑动期间维护一个record【26】用来记录子串字符出现的次数,用于找最大重复字符。
  3. 注意更新的最大重复字符的时候,只与添加的s[j+1]字符和滑动时s[i]字符有关系!

代码:

class Solution {
public:
	int characterReplacement(string s, int k) {
		int ssize = s.size();
		if (ssize == 0)return 0;
		//int wsize = 1;
		int i = 0;
		int j = 0;
		vector<int> record(26, 0);
		char maxc = s[0];

		record[s[i] - 'A'] = 1;
		while (j < ssize-1) {
			++j;
			record[s[j] - 'A']++;
			if (record[s[j] - 'A'] > record[maxc - 'A'])maxc = s[j];

			if (j - i + 1 > record[maxc - 'A'] + k) {
				record[s[i] - 'A']--;
				++i;
				if (record[s[j] - 'A'] > record[maxc - 'A'])maxc = s[j];
			}

		}
		
		
		return j-i+1;
	}
};

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值