目录

题目描述

面试题 05.03. 翻转数位

题意分析

把一个 32 位整数的某一位从 0 翻成 1,求翻转后最长连续 1 的长度。只能翻 0 → 1,不能把 1 翻成 0;即使原数已经全是 1,答案也最多是 32。

问题等价于:在固定的 32 位二进制序列中,找一个最多包含一个 0 的最长连续区间。区间中的那个 0 就是被翻转的位置;若区间本来全是 1,可以把区间外的某个 0 翻转,长度不受影响。

必须扫描满 32 位,不能写成 while (num != 0)。高位补零也是合法的可翻转位,例如 num = 0 的答案应为 1;提前在数值变成零时停止会漏掉这些位。

解法:固定宽度滑动窗口

核心思路

暴力做法可以依次翻转 32 个位置,每次再扫描 32 位求最长连续 1。它在固定 32 位下也能通过,但没有抓住题目的结构;若推广到长度为 n 的位串,就会变成 $O(n^2)$。

用窗口 [j, i] 表示当前候选区间,cnt 记录窗口里的 0 的数量。每加入第 i 位,就把 bit ^ 1 加进 cnt:位为 1 时贡献 0,位为 0 时贡献 1。若 cnt > 1,不断右移左端 j,并扣掉移出位对 0 数量的贡献,直到窗口重新至多含一个 0。

循环不变量是:更新答案前,[j, i] 是以 i 结尾、最多包含一个 0 的最长区间。任何更靠左的起点都会含两个 0,因此不可能合法;所以用 i - j + 1 更新全局最大值不会漏解。

解题步骤

  • 初始化左端点 j = 0、窗口零数 cnt = 0、答案 answer = 0
  • i = 0..31 从低位到高位扫描;连续区间反向读取仍然连续,不影响长度。
  • 取出第 i 位,若为 0 就让 cnt 加一。
  • cnt > 1 时移动 j,移出的位若为 0,同时把 cnt 减一。
  • 此时窗口合法,用其长度更新答案。

num = 1775 为例,二进制是 11011101111,两段较长的 1 分别是 1111111,中间只隔一个 0。窗口扩到第二个 0 时会收缩掉更早的那段;最终保留 1110 1111,翻转中间的 0 后得到连续 8 个 1,因此答案为 8。

代码实现

// 窗口中最多允许一个 0;这个 0 就是要翻转的位。
class Solution {
    public int reverseBits(int num) {
        int answer = 0, cnt = 0;
        for (int i = 0, j = 0; i < 32; ++i) {
            cnt += num >> i & 1 ^ 1;
            while (cnt > 1) {
                cnt -= num >> j & 1 ^ 1;
                ++j;
            }
            answer = Math.max(answer, i - j + 1);
        }
        return answer;
    }
}
// 窗口中最多允许一个 0;这个 0 就是要翻转的位。
func reverseBits(num int) (answer int) {
    var cnt, j int
    for i := 0; i < 32; i++ {
        cnt += num>>i&1 ^ 1
        for cnt > 1 {
            cnt -= num>>j&1 ^ 1
            j++
        }
        answer = max(answer, i-j+1)
    }
    return
}

复杂度分析

  • 时间复杂度:对 32 位整数固定扫描 32 次,严格来说是 $O(1)$;推广到长度为 b 的位串是 $O(b)$,左右指针都至多移动 b 次。
  • 空间复杂度:$O(1)$,只维护两个指针、窗口零数和答案。

关键点总结

  • “允许翻转一个 0”就是“窗口内最多允许一个 0”,这是把修改次数转成窗口预算的典型模型。
  • bit ^ 1 只是在 0/1 间取反,用来直接累计零的数量;面试时写成 if (bit == 0) cnt++ 也完全可以,清晰优先。
  • 面试官若追问“允许翻转 k 个 0”,只需把收缩条件从 cnt > 1 改成 cnt > k,就是 LeetCode 1004。
  • 固定宽度整数题要主动说明扫描 32 位,尤其要覆盖 num = 0 与全 1 两个边界。

易错点总结

  • 只扫描到最高有效位num = 0 时循环一次都不执行,错误返回 0;固定扫描 32 位才能得到 1。
  • 窗口允许两个 0:把收缩条件误写成 cnt > 2num = 0b10101 会把需要翻两个 0 的长度 5 当成答案,正确答案是 3。
  • 收缩时忘记扣掉左端位的贡献cnt 只增不减,遇到第二个 0 后窗口会一直收缩到末尾。
  • 不加括号直接解释位运算优先级:代码虽然可运行,但白板表达容易产生歧义;讲解时应明确写成 ((num >> i) & 1) ^ 1

相似题目

题目 难度 考察点
424. 替换后的最长重复字符 中等 替换窗口
485. 最大连续 1 的个数 简单 替换窗口
487. 最大连续1的个数 II 中等 替换窗口
1004. 最大连续1的个数 III 中等 替换窗口
1493. 删掉一个元素以后全为 1 的最长子数组 中等 替换窗口