在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。
返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。
示例 1:
输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。
示例 2:
输入:A = [1,1,0], K = 2
输出:-1
解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。
示例 3:
输入:A = [0,0,0,1,0,1,1,0], K = 3
输出:3
解释:
翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0]
翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0]
翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1]
提示:
1 <= A.length <= 30000
1 <= K <= A.length
分析:
-
本题采用暴力破解超时,参考了别人的思路后,问题在于如何避免暴力方法中内层循环不停的对i后的K个值不停的翻转。
-
观察题目发现数组A中只有0,1两种情况,目的是想全部置为1,那么对于0,要翻转奇数次,对于1要翻转偶数次才能达到目的,
而每一个当前元素要不要翻转,一方面要看自己是0还是1,另一方面要看该元素前面的元素翻转了多少次。
-
由此可以看出,如果我们能有一个数据结构记录那些需要翻转的元素信息,就无需对于A[i]元素进行重复的0->1,1->0的操作,在这里我们利用队列先进先出的特性来伴随着数组A进行从左到右的遍历。而队列的实时长度也就反应了当前元素已经被前面的元素的翻转窗口影响(翻转)了多少次,代码及注释如下:
class Solution {
public:
int minKBitFlips(vector<int>& A, int K) {
// 借助队列,其中的每一个元素的值表示从A中的哪个位置开始进行一次长度为K的翻转
queue<int> q;
int i = 0;
int reverseTimes = 0;
while(i < A.size()){
// 遇到i >= q.front() + K,说明已经走完队列的第一个元素翻转影响的窗口
// 队首出队,对于当前的i已经不受q.pop的影响了
if(q.size() > 0 && i >= q.front() + K) q.pop();
// 想象上来去找第一个需要翻转的0时,q.size与A[i]满足的数量关系
// 对于遍历到的每一个元素,q.size()表示当前元素已经被翻转了q.size()次
// 利用了对于0应该翻转奇数次,对于1应该翻转偶数次
if(q.size() % 2 == A[i]){
if(i + K > A.size()) return -1;
q.push(i); // 存的是元素的下标,可以用来记录遍历到的位置
++reverseTimes;
}
++i;
}
return reverseTimes;
}
};