引言
在今天的文章中,我们将聚焦于一个与数组处理相关的算法问题——最大连续1的个数(允许翻转K个0)。这个问题要求我们在一个二进制数组(即只包含0和1的数组)中,通过翻转最多k个0为1,来找出最长的连续1子数组的长度。为了高效地解决这个问题,我们将采用滑动窗口这一强大的技术。
leetcode-最大连续1的个数
问题描述
给定一个二进制数组 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。
解题思路
问题转化:找出最长的子数组,0的个数不超过k个。
思路1:暴力法(超时)
遍历所有可能的子数组,并检查每个子数组中0的个数。如果0的个数不超过k,则更新最大长度。然而,这种方法的时间复杂度为O(n^3),在数组较大时会超时。
思路2:滑动窗口,如下图所示
- 初始化:设置两个指针left和right作为窗口的左右边界,初始时都指向数组的开始位置。同时,设置变量zero来记录窗口中0的个数,初始值为0。变量len用于记录最长的连续1子数组的长度,初始值为0。
- 进窗口(移动右边界):通过循环,将右边界right向右移动,并检查当前元素是否为0。如果是,则zero加1。
- 判断,出窗口(调整窗口大小):在每次移动右边界后,检查zero是否大于k。如果是,说明窗口中0的个数超过了允许的范围,需要移动左边界left来缩小窗口,直到zero小于或等于k。在移动左边界的过程中,如果左边界指向的元素是0,则zero减1。
- 更新结果:在每次移动右边界或左边界后,计算当前窗口的长度(即right - left + 1),并将其与Len比较,更新len为较大值。
- 循环结束:当右边界right遍历完整个数组后,循环结束,此时len中存储的就是最长的连续1子数组的长度。
- 返回结果:返回len作为最终结果。
代码实现
以下是使用暴力法,C++实现的代码示例: (超时)
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int left , right ,zero = 0, len=0;
int n = nums.size();
for(left = 0; left < n; left++) {
zero = 0;
for(right = left; right < n ; right++){
if(nums[right]==0) zero++;
if(zero > k) break;
len = max(len , right - left + 1);
}
}
return len;
}
};
以下是使用滑动窗口,C++实现的代码示例:
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int left , right ,zero = 0, len=0;
int n = nums.size();
for(left = 0,right = 0; right < n; right++){
if(nums[right]==0)
zero++;
while(zero>k){
if(nums[left]==0)
zero--;
left++;
}
len = max(len , right-left+1);
}
return len;
}
};
这段代码通过滑动窗口技术高效地解决了问题,时间复杂度为O(n),其中n是数组的长度。