各位题友大家好! 今天是 @负雪明烛 坚持日更的第 25 天。今天力扣上的每日一题是「995. K 连续位的最小翻转次数」。
解题思路
题目大意:每次翻转长度为 K 的子数组,求最少的翻转次数使数组中所有的 0 都更改为 1。如果不能实现,则返回 -1.
- 结论 1:后面区间的翻转,不会影响前面的元素。因此可以使用贪心策略,从左到右遍历,遇到每个 0 都把它和后面的 K 个数进行翻转。
- 结论 2:$A[i]$ 翻转偶数次的结果是 $A[i]$;翻转奇数次的结果是 $A[i] ^ 1$。
方法一:模拟翻转(超时)
一个直观的思路是,从左到右遍历一遍,遇到数字为 0,那么翻转以该数字为起始的 K 个数字。
代码如下:
class Solution(object):
def minKBitFlips(self, A, K):
"""
:type A: List[int]
:type K: int
:rtype: int
"""
N = len(A)
res = 0
for i in range(N - K + 1):
if A[i] == 1:
continue
for j in range(K):
A[i + j] ^= 1
res += 1
for i in range(N):
if A[i] == 0:
return -1
return res
- 时间复杂度:$O(N * K + N)$,超时。
- 空间复杂度:$O(1)$。
方法二:滑动窗口
上面方法超时的主要原因是我们真实地进行了翻转。根据结论二,位置 i 现在的状态,和它被前面 K - 1 个元素翻转的次数(奇偶性)有关。
我们使用队列模拟滑动窗口,该滑动窗口的含义是前面 K - 1 个元素中,以哪些位置起始的窗口进行了翻转。该滑动窗口从左向右滑动,如果当前位置 i 需要翻转,则把该位置存储到队列中。遍历到新位置 j (j < i + K)时,队列中元素的个数代表了 i 被前面 K - 1 个元素翻转的次数。
- 当 i 位置被翻转了偶数次,如果 $A[i]$ 为 0,那么翻转后仍是 0,当前元素需要翻转;
- 当 i 位置被翻转了奇数次,如果 $A[i]$ 为 1,那么翻转后是 0,当前元素需要翻转。
综合上面两点,我们得到一个结论,如果 $len(que) % 2 == A[i]$ 时,当前元素需要翻转。
class Solution(object):
def minKBitFlips(self, A, K):
"""
:type A: List[int]
:type K: int
:rtype: int
"""
N = len(A)
que = collections.deque()
res = 0
for i in range(N):
if que and i >= que[0] + K:
que.popleft()
if len(que) % 2 == A[i]:
if i + K > N: return -1
que.append(i)
res += 1
return res
刷题心得
今天的题目理解着比较困难,主要是语言很难说明白,其实理解之后没有那么难。
参考资料:
1. Grandyang
2. 官方题解:K 连续位的最小翻转次数
3. 官方答案太难懂啦,滑动窗口通俗易懂。
OK,以上就是 @负雪明烛 写的今天题解的全部内容了,如果你觉得有帮助的话,求赞、求关注、求收藏。如果有疑问的话,请在下面评论,我会及时解答。
关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。
祝大家牛年大吉!AC 多多,Offer 多多!我们明天再见!