题目描述

✅ 995. K 连续位的最小翻转次数

image-20260929105529855

image-20260929105530055

题意分析

每次选择长度恰好为 k 的连续区间,把其中所有位取反,求把整个二进制数组变成全 1 的最少次数;若不存在合法操作方案,返回 -1。

区间翻转相当于逐位异或,操作顺序不影响最终结果,同一起点翻转两次会完全抵消。因此最优方案中,每个合法起点最多翻转一次,可以按起点从左到右决定是否操作。

解法:强制贪心 + 翻转差分

核心思路

[!blue]

扫描到位置 i 时,所有小于 i 的起点都已经决定,位置 0 到 i - 1 也已经修正。尚未决定的起点中,只有 i 本身能覆盖当前位置,任何更靠右的起点都无法回头修改它。因此当前有效值为 0 时,必须从 i 开始翻转;为 1 时必须不翻,否则后续再也无法把它修正。

这个选择是每个可行方案都必须遵守的,不是局部尝试。逐位执行后,若需要翻转时发现 i + k > n,说明唯一能修正当前位置的操作也不合法,任何方案都无解;若顺利走到末尾,每一位都已修正。由于每个起点只执行被迫需要的一次翻转,得到的次数也必然最少。

不必真的逐格改写每个长度为 k 的窗口。用 flip 记录覆盖当前位置的已选翻转次数的奇偶性,当前位置的有效值就是 nums[i] ^ flip。偶数次翻转等于不变,奇数次翻转等于取反,所以只需维护 0 或 1。

从 i 开始的翻转覆盖 [i, i + k - 1],到 i + k 才失效。新增翻转时立即执行 flip ^= 1,让后面的扫描看到它的影响,同时登记 diff[i + k] ^= 1。每次到达一个位置,先执行 flip ^= diff[i] 移除恰好到期的影响,再判断当前有效值,避免把已经结束的窗口继续算进去。

解题步骤

  1. 建立失效标记数组和当前翻转奇偶。
  2. 扫描每个位置,先撤销到此结束的翻转。
  3. 有效值为零时,检查剩余长度是否足够放入窗口。
  4. 足够则计数并立即生效,同时登记失效位置。

i + k == n 时窗口恰好到数组末尾,仍然合法;diff 分配 n + 1 个位置,是为了允许登记下标 n 的失效事件,即使扫描不会再访问它。k == 1 时每个原始零各翻一次;数组已经全为一时不会产生操作,答案为零。原数组始终保留原值,所有影响由翻转奇偶表示。

代码实现

class Solution {
    public int minKBitFlips(int[] nums, int k) {
        int n = nums.length;
        // diff[i] 为 1 表示有一次翻转在位置 i 处失效。
        int[] diff = new int[n + 1];
        // flip 表示当前位置累计被翻转次数的奇偶性。
        int flip = 0;
        int answer = 0;

        for (int i = 0; i < n; i++) {
            flip ^= diff[i];

            if ((nums[i] ^ flip) == 0) {
                // 只有以 i 为起点才能修正位置 i,放不下就无解。
                if (i + k > n) {
                    return -1;
                }

                answer++;
                flip ^= 1;
                diff[i + k] ^= 1;
            }
        }

        return answer;
    }
}
func minKBitFlips(nums []int, k int) int {
    n := len(nums)
    // diff[i] 为 1 表示有一次翻转在位置 i 处失效。
    diff := make([]int, n+1)
    // flip 表示当前位置累计被翻转次数的奇偶性。
    flip := 0
    answer := 0

    for i := 0; i < n; i++ {
        flip ^= diff[i]
        if (nums[i] ^ flip) == 0 {
            // 只有以 i 为起点才能修正位置 i,放不下就无解。
            if i+k > n {
                return -1
            }
            answer++
            flip ^= 1
            diff[i+k] ^= 1
        }
    }
    return answer
}

复杂度分析

  • 时间复杂度:$O(n)$。
  • 空间复杂度:$O(n)$,失效标记数组。

关键点总结

[!green]

  • 先处理失效事件,再判断当前位置。
  • 真实值由原值和生效翻转奇偶共同决定。
  • i+k=n 仍是合法窗口,标记数组为这个失效位置留出空间。

易错点总结

[!yellow]

  • 只看原数组中的零:此前翻转会改变当前有效值。
  • 新增翻转不立即改变 flip:后续位置看不到本次影响。
  • 不登记窗口结束:影响错误延续到数组末尾。
  • 把恰好到末尾的窗口判为越界:漏掉合法最后起点。

相似题目

题目 难度 关联与区别
1529. 最少的后缀翻转次数 中等 两题遇到最左错误位时操作都被强制,本题翻固定长度窗口,原题翻整个后缀。
1109. 航班预订统计 中等 同样可用起止事件记录区间操作影响,本题只关心当前覆盖的翻转次数奇偶。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2023/54960587
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!