LeetCode说:“过年的好日子过完了”,今天就来了个困难题
LeetCode上K 连续位的最小翻转次数,困难难度,用了2种方法,方法一是自己的直接操作方法,方法二是参考了官方题解的滑动窗口方法,记录下解题思路
由0/1组成的数组,K为每次翻转连续元素的数量,需要把数组翻转为全为1的数组
假设传入数组为A = [0,0,0,1,0,1,1,0],K=3,那么翻转的过程如下
每次需要翻转K长度的元素
第一次翻转,因为第0位是0,所以翻转[0,K-1]范围的元素,即[0,2]
翻转之后可得,也可以看出i = 3
对应的元素是1,是不需要翻转的
所以第二次翻转,应该翻转的是[4,6]
翻转之后可得
第三次翻转,翻转[5,7]
翻转后结果,此时全数组均为1,完成处理
上面传入的数组最终翻转3次即可完成操作,那么可以看下对应的每次翻转是怎么完成的:
第一次翻转
i = 0 K=3
需要翻转0开始向后3位元素,并且A[0] = 0,是需要翻转的,这里不管后面两个元素直接翻转
之后i++
i = 1
,A[1] = 0,翻转之后为1,那么遍历到这个元素的时候不需要翻转,就i继续++
i = 2
,A[2] = 0,翻转之后为1,也是无需翻转i++
i = 3
,A[3] = 1,直接为1,也是不需要翻转继续i++
i = 4
,A[4] = 0,这里开始继续翻转K长度的元素,是第二次翻转
i = 5
,A[5] = 1,但是之前翻转过了为0,所以还需要翻转K长度元素,是第三次翻转
之后i=6/7
的情况都为1,那么就是无需翻转的
这里可以总结出来,遍历整个数组,如果当前位为1,就不需要翻转K长度元素,如果为0就翻转K长度元素,这样的逻辑就能完成题目,可以写出如下程序。
方法一:直接翻转
var minKBitFlips = function(A, K) {
// 保存A的长度
let n = A.length;
// 保存结果
let res = 0
// 遍历整个A
for(let i=0;i<n;i++) {
if(A[i] === 0 && i+K<=n) {
// 要限制好范围,如果当前位置+K的长度已经超过数组了,那就不要翻转
// 如果为0就要翻转K长度元素
for(let j = i;j<i+K;j++) {
// 对当前元素取反
A[j] = Number(!A[j])
}
// 记为一次翻转
res++
} else {
// 如果不为0就跳过
continue
}
}
// 将整个数组求和,这个和如果等于数组长度,那么就是全为1
// 代表数组翻转成功
let sum = A.reduce((a,b) => {return a + b},0)
// 如果不等于长度,那么就是中间还有0,就返回-1,表示无法翻转
if(sum !== n) return -1
return res
};
之前担心会超时,但是提交了一下,居然可以通过,那就作为第一种方法写出来,方便理解吧
方法二:滑动窗口法
第一种方法是直接操作了数组,用时会比较长,第二种方法参考官方题解,可以这样理解:
c
:是一个标志位,用于记录当前元素是否翻转过
例子还是传入了A = [0,0,0,1,0,1,1,0],K=3
,设一开始的时候c=0
- 当
i=0
的时候
A[i] = 0
,通过判断A[i] === c?
来确定是否需要翻转,相等就翻转,不等就不需要,这里不操作原数组,将A[i] = 0 + 2
表示翻转,c
因为当前位置翻转了,就要进行取反操作,此时c = 1
,并且原数组变为[2,0,0,1,0,1,1,0]
这里是第一次翻转 - 当
i=1
的时候
A[1] = 0
,其实这里应该视为翻转了,但是之前只操作了c
让c = 1
,所以这里不需要翻转 - 当
i=2
的时候
A[2] = 0
,和上面的情况一样 - 当
i=3
的时候
A[3] = 1
,但是此时i已经超过了之前K的范围了,并且会出现一个判断A[i-K] > 1
,这里就是上面+2的原因,A[3-3]=A[0]=2>1,那么c就要重新设为0,表示这个区间内目前是没翻转的,那么根据A[i] === c?
,对应元素不需要翻转 - 当
i=4
的时候
A[4] = 0
,根据A[i] === c?
,对应元素翻转,不操作原数组,给c取反c=1
,并且将对应元素+2让A[4] = 2
,此时原数组变为[2,0,0,1,2,1,1,0]
第二次翻转 - 当
i=5
的时候
A[5] = 1
,但是之前翻转了,并且c = 1
,所以这里需要翻转,再给c取反c=0
,并且将对应元素+2让A[5] = 3
,此时原数组变为[2,0,0,1,2,3,1,0]
,这里是第三次翻转 - 当
i=6
的时候
A[6] = 1
,此时c为0,那么是不相等的,不需要翻转 - 当
i=7
的时候
A[7] = 0
,此时会出现一个判断A[i-K] > 1
,这里判断过后会将c
设为1,表示当前位置翻转过一次,此时不需要翻转了,可以看之前的图
第三次翻转之后,整个数组都为1了
所以遍历到这个i=7
的时候是不需要翻转的
可以写出下面的代码,直接抄官方的,主要看下理解过程吧
var minKBitFlips = function(A, K) {
// 保存A的长度
let n = A.length;
// 保存结果
let res = 0
// c表示实付需要翻转
let c = 0;
// 遍历整个数组
for (let i = 0; i < n; ++i) {
// 当超过当前窗口这个标志位需要取反
// 当前k位>1的时候,表示当前元素已经被翻转过了
if (i >= K && A[i - K] > 1) {
// 对c取反
c = Number(!c)
}
if (A[i] == c) {
// 判断边界问题
if (i + K > n) {
return -1;
}
++res;
c = Number(!c)
// +2表示这个位置开始K位翻转了
// 因为数组由0/1组成,+2直接能跳过这两个数
A[i] += 2;
}
}
return res;
};
这样就会快很多,主要是借用一个标志位,不去操作原数组,速度会更快