滑动窗口_最大连续1的个数|||_C++
1.题目解析
leedcode题目链接:https://leetcode.cn/problems/max-consecutive-ones-iii/submissions/
给定一个二进制数组 nums
和一个整数 k
,如果可以翻转最多 k
个 0
,则返回数组中连续 1
的最大个数 。
示例 1:
输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
2.算法原理
2.1解法一:暴力枚举+zero计数器
如果我们真的按题目中的理解,让0翻转成1的话,将会非常麻烦。我们可以将问题转化成:找出最长的连续子数组,0的个数不超过k
个。我们可以设置一个zero
来记录0出现了多少次。
以:[1,1,1,0,0,0,1,1,1,1,0]
,k = 2
为例:
我们先定义两个指针,left
记录起始位置,right
记录终止位置。最开始时,left
和right
都指向下标为0的位置。如果right
指向的数据为1,就直接让right++
,跳过这个数据;如果为0,就让zero++
,然后再让right++
,表示0又出现了一次。如图,当right
指向图中位置时,zero
的值已经为2了,且当前位置的值是0,说明已经找到了一个结果,记录这个结果。
随后我们可以让left++
,让right = left
,接着以下标为1的位置为起始位置,再遍历一次,就这样,遍历出最终结果。
2.2解法二:滑动窗口
优质的算法往往都是依据暴力枚举法优化而来的。还是以[1,1,1,0,0,0,1,1,1,1,0]
,k = 2
为例:
当找到一个结果后,指针指向图中的位置。这时,我们还需要让left++
,然后让right
回退吗?答案是不需要。
不需要的原因是,我们遇到了一串连续的0,不管left
指针从前三个1的那一个1开始遍历,都没有第一个1的结果大,right
指针都会在连续的0处终止。这时,我们的思路就是,跳过这串连续的0。
我们固定right
不动,让left
一直++
,遇到1直接跳过,遇到0就让zero--
,表示窗口中的0减少了一个。我们一直让left
移动到连续的0的第二个0处,从这个0开始移动right
,再去寻找下一个结果。
3.代码演示
1.写法1:
class Solution {
public:
int longestOnes(vector<int>& nums, int k)
{
int left = 0, right = 0;
int n = nums.size();
int ret;
int zero = 0; // 记录0出现的次数
while(right < n)
{
while(right < n && (nums[right] || zero < k)) // 进窗口
{
if(!nums[right]) zero++;
right++;
}
ret = max(ret, right - left); // 更新结果
while(left < n && zero >= k) // 出窗口
{
if(!nums[left]) zero--;
left++;
}
}
return ret;
}
};
2.写法2:
class Solution {
public:
int longestOnes(vector<int>& nums, int k)
{
int ret = 0;
for(int left = 0, right = 0, zero = 0; right < nums.size(); right++)
{
if(nums[right] == 0) zero++; // 进窗口
while(zero > k) // 判断
if(nums[left++] == 0) zero--; // 出窗口
ret = max(ret, right - left + 1); // 更新结果
}
return ret;
}
};
这两个写法的本质是一样的,都是滑动窗口的原理。