题目来源
题目描述
class Solution {
public:
int minKBitFlips(vector<int>& nums, int k) {
}
};
题目解析
题意:给定一个只有0和1的数组,有给定一个数字k,每次可以翻转任意连续的k个位置的数字,至少需要多少步可以将0全部变成1。如果不能实现,则返回-1
分析:
- 对于每个位置,只有初始状态和总共被反转了多少次决定了自己最终的状态
- 每个长度为k的区间,最多只会被反转一次,因为两次反转后对最终结果没有影响
得到如下结论:
- 结论一:后面区间的反转,不会影响前面的元素。因此可以使用贪心,从左到右遍历,每次遇到0时就把它和它后面的k-1个元素进行翻转
- 结论二:
A
[
i
]
A[i]
A[i]翻转偶数次的结果是
A
[
i
]
A[i]
A[i],翻转奇数次的结果是
A[i] ^ 1
模拟翻转(超时)
一个直观的思路是,从前往后遍历数组,如果遇到一个 0,我们将当前位置开始的长度为 k 区间的区间反转。如果遇到0时,剩下的区间长度不足k说明我们没有办法完成反转。
class Solution {
public:
// 1 <= nums.length <= 10^5
// 1 <= k <= nums.length
int minKBitFlips(vector<int>& nums, int k) {
int step = 0;
int N = nums.size();
for (int i = 0; i < N; ++i) {
if(nums[i] == 0){
if(i + k > N){
return -1;
}
for (int j = 0; j < k; ++j) {
nums[i + j] ^= 1;
}
step++;
}
}
//
return step;
}
};
- 时间复杂度: O(N * K + N),超时
- 空间复杂度: O(1)
滑动窗口
上面超时的原因是我们真实的进行了反转。根据结论二,位置i现在的状态,和它被前面 k − 1 k - 1 k−1个元素翻转的次数(奇偶性)有关。
我们使用队列模拟滑动窗口,该滑动窗口的含义是前面 k − 1 k - 1 k−1个元素中,以哪些位置起始的子区间进行了翻转。
该滑动窗口从左到右滑动,如果当前位置 i i i需要翻转,则把该位置存储到队列中。遍历到新位置j ( j < i + K ) (j < i + K) (j<i+K)时,队列中元素的个数代表了i被前面k-1个元素翻转的次数。
- 当 A [ i ] A[i] A[i]为0,如果i位置被翻转了偶数次,那么翻转后依然是0,当前元素需要被翻转
- 当 A [ i ] A[i] A[i]为1,如果 i 位置被翻转了奇数次,那么翻转后变成 0,当前元素需要翻转。
即,当que.size() % 2 == A[i]时,当前元素需要翻转。
当i + K > N4 时,说明需要翻转大小为 K 的子区间,但是后面剩余的元素不到 K 个了,所以返回 -1。
class Solution {
public:
int minKBitFlips(vector<int>& nums, int k) {
int N = nums.size();
std::queue<int> que;
int res = 0;
for (int i = 0; i < N; ++i) {
if(!que.empty() && i >= que.front() + k){
que.pop();
}
if(que.size() % 2 == nums[i]){
if(i + k > N){
return -1;
}
que.push(i);
res++;
}
}
return res;
}
};