原题
In an array A containing only 0s and 1s, a K-bit flip consists of choosing a (contiguous) subarray of length K and simultaneously changing every 0 in the subarray to 1, and every 1 in the subarray to 0.
Return the minimum number of K-bit flips required so that there is no 0 in the array. If it is not possible, return -1.
Example 1:
Input: A = [0,1,0], K = 1
Output: 2
Explanation: Flip A[0], then flip A[2].
Example 2:
Input: A = [1,1,0], K = 2
Output: -1
Explanation: No matter how we flip subarrays of size 2, we can't make the array become [1,1,1].
Example 3:
Input: A = [0,0,0,1,0,1,1,0], K = 3
Output: 3
Explanation:
Flip A[0],A[1],A[2]: A becomes [1,1,1,1,0,1,1,0]
Flip A[4],A[5],A[6]: A becomes [1,1,1,1,1,0,0,0]
Flip A[5],A[6],A[7]: A becomes [1,1,1,1,1,1,1,1]
Note:
1 <= A.length <= 30000
1 <= K <= A.length
分析
在给出一个0, 1序列后,可以对其中连续K个数字进行翻转(0翻转为1,1翻转为0)。如果想要全部翻转为1,那么直观的一个感受就是从左到右,如果当前位置的数字是0,那么可以作为需要翻转的连续K个数字的起点,因此最简单的思路:
- 从左到右,寻找值为0的下标index
- 对A[index, index + K-1]的数字进行翻转
- 重复1, 2,直至数组值全为1,或者index + K > A.length为止
拍脑门解法
Note:需要注意的一点是,在进行翻转的时候所采取的方法决定是否超时,用^进行异或操作会减少用时,特别地,与1异或表示对当前位进行翻转
Accept代码
class Solution {
public:
bool flip(vector<int> & A, int K, int pos) {
if (pos + K > A.size())
return false;
for (int i = pos; i < pos + K; ++i) {
A[i] ^= 1;
}
return true;
}
int minKBitFlips(vector<int>& A, int K) {
int n = A.size();
int ans = 0, last_pos = 0;
for (int i = 0; i < n; ++i) {
if (A[i] == 0) {
ans++;
if (!flip(A, K, i))
return -1;
}
}
return ans;
}
};
在上面的解法中,之前用if else语句对A数组值进行翻转,结果超时。
bool flip(vector<int> & A, int K, int pos) {
if (pos + K > A.size())
return false;
for (int i = pos; i < pos + K; ++i) {
//超时代码
if(A[i] == 1)
A[i] = 0;
else
A[i] = 1;
}
return true;
}
这个方法速度很慢,用时5216 ms,在超时的边缘徘徊,于是借鉴别人的解法。
他山之石,可以攻玉(别人的答案)
在提交之后可以看到别人提交的答案,看完后再看看自己的代码便更觉得羞涩难挡,因此去掉了贴自己代码这一步,转而分析别人代码。
作者@awice
思路:
之前的贪心算法虽然可以成功运行,但是耗时较长(主要耗时的部分在翻转A[index, index+K-1]部分,当次翻转范围与上次翻转范围存在重叠,当重叠部分进行翻转的两次的时候,相当于没有翻转,造成了时间的浪费)。因此用一个flip位来记录当前K个连续数字是否已经翻转,用一个hint数组来记录翻转。
算法流程:
-
初始化flip =0, hint数组为0
-
遍历数组A,
-
flip ^= hint[i], 异或运算后flip位表示当前位置i是否被翻转过,flip=0,未翻转,flip=1,翻转。hint数组用来记录翻转,(特别地,当A[i]需要翻转时,hint[i + K]记录翻转截止范围,见下面第4步骤)
-
判断A[i] == flip,
- A[i]与flip均为0时,表示A[i] = 0且当前位未被翻转,需要进行翻转
- A[i]与flip均为1时,表示A[i] = 1且当前位已被翻转(实际值为0),需要再次翻转
若A[i] == flip, 表明需要进行翻转
- 翻转次数加一,ans++
- 判断i+K是否超出A数组长度,超出则失败返回-1
- 对flip位进行翻转
- 对hint[i+K]位进行翻转
class Solution {
public:
int minKBitFlips(vector<int>& A, int K) {
int n = A.size();
vector<int> hint(n, 0);
int ans = 0, flip = 0;
for (int i = 0; i < n; ++i) {
flip ^= hint[i];
if (A[i] == flip) {
ans++;
if (i + K > n)
return -1;
flip ^= 1;
if (i + K < n)
hint[i + K] ^= 1;
}
}
return ans;
}
};
使用改进算法对重叠的部分进行索引记录后,程序运行时间大大减少,为112ms,缩短了近50倍。果然还是别人的饭吃起来香~