LeetCode 201. 数字范围按位与:位运算高效解题指南

在刷LeetCode的过程中,「数字范围按位与」是一道非常经典的位运算题目,它看似简单,却藏着位运算的核心思维------抓共性、弃差异。今天就来详细拆解这道题,从问题本质到代码实现,帮你彻底搞懂背后的逻辑,轻松掌握解题技巧。

一、题目回顾:明确需求

题目很简洁:给定两个整数 left 和 right,代表区间 [left, right],返回这个区间内所有数字按位与的结果(包含左右两个端点)。

举个例子帮助理解:

  • 当 left = 5,right = 7 时,区间内数字为 5(101)、6(110)、7(111),按位与结果为 100(4);

  • 当 left = 0,right = 0 时,结果就是 0;

  • 当 left = 1,right = 3 时,数字为 1(01)、2(10)、3(11),按位与结果为 0。

二、痛点分析:为什么不能暴力求解?

看到这道题,很多人的第一反应是「暴力遍历」:从 left 遍历到 right,依次对每个数字做按位与操作。但这种方法存在一个致命问题------效率极低。

如果 left 和 right 的范围很大(比如 left = 1,right = 10^9),遍历一次需要执行 10^9 次操作,会直接超时。所以,我们必须找到一种更高效的方法,跳出「遍历所有数字」的思维定式。

三、核心原理:按位与的特性 + 公共前缀

要解决这道题,首先要掌握「按位与」的核心特性:只要有一个数字的某一位是 0,那么所有数字在这一位上的按位与结果就是 0

结合区间 [left, right] 的特点:区间内的数字是连续的,从 left 递增到 right,二进制表示中,高位的公共前缀是不变的,而低位会从 0 变到 1,再从 1 变到 0

举个具体的例子,left = 5(101),right = 7(111):

  • 5 的二进制:1 0 1

  • 6 的二进制:1 1 0

  • 7 的二进制:1 1 1

观察这三个数字的二进制,高位只有最左边的「1」是所有数字共有的(公共前缀),后面的两位(低位)在区间内出现了 0,所以按位与结果中,公共前缀保留,低位全部变为 0,最终结果就是 100(4)。

由此可得出解题核心:区间内所有数字的按位与结果,等于 left 和 right 二进制表示的「公共前缀」,后面补 0

四、代码解析:如何找到公共前缀?

知道了核心原理,接下来就是如何用代码找到 left 和 right 的公共前缀。这里用到了一个巧妙的操作------右移,具体逻辑如下:

typescript 复制代码
function rangeBitwiseAnd(left: number, right: number): number {
  let shift = 0;
  // 找到公共前缀:不断右移,直到left和right相等
  while (left < right) {
    left >>= 1;  // left右移1位,相当于去掉最右边的一位
    right >>= 1; // right右移1位,去掉最右边的一位
    ++shift;      // 记录右移的次数(也就是需要补0的位数)
  }
  // 把公共前缀左移shift位,补全后面的0
  return left << shift;
};

代码逐行拆解:

  1. 定义 shift 变量,用于记录右移的次数(也就是公共前缀后面需要补 0 的位数);

  2. 循环条件:当 left < right 时,说明两者的二进制还没有完全重合(公共前缀还没找到);

  3. left >>= 1 和 right >>= 1:将 left 和 right 同时右移 1 位,相当于去掉各自最右边的一位,逐步向公共前缀靠近;

  4. 循环结束后,left 和 right 已经相等,此时的值就是两者的公共前缀;

  5. left << shift:将公共前缀左移 shift 位,补全后面的 0,得到最终的按位与结果。

五、实例验证:代入代码看效果

还是以 left = 5(101),right = 7(111)为例:

  1. 初始:left = 5(101),right = 7(111),shift = 0;left < right,进入循环;

  2. 第一次循环:left = 5 >> 1 = 2(10),right = 7 >> 1 = 3(11),shift = 1;left < right,继续循环;

  3. 第二次循环:left = 2 >> 1 = 1(1),right = 3 >> 1 = 1(1),shift = 2;left = right,循环结束;

  4. 返回 left << shift = 1 << 2 = 4(100),与预期结果一致。

六、边界情况补充

这道题的边界情况需要特别注意,避免踩坑:

  • 当 left = right 时:区间内只有一个数字,结果就是 left(或 right),此时循环不执行,shift = 0,返回 left << 0 = left,符合预期;

  • 当 left = 0 时:无论 right 是多少,区间内包含 0,按位与结果一定是 0(因为 0 与任何数按位与都是 0),代码也能正确处理(比如 left=0,right=5,循环后 left 和 right 都会变成 0,shift 为 3,返回 0 << 3 = 0);

  • 当区间跨过大数(如 left=109,right=109+5):代码只需执行几次右移就会找到公共前缀,效率极高,不会超时。

七、总结:解题思维提炼

这道题的关键,是跳出「暴力遍历」的思维,抓住「按位与的特性」和「区间数字的二进制共性」------连续数字的二进制,只有高位公共前缀是不变的,低位一定会出现 0,导致按位与结果为 0。

通过「右移找公共前缀,左移补 0」的操作,我们将时间复杂度从 O(n) 优化到了 O(log n)(n 为 right - left 的差值),这也是位运算题目最核心的解题思路:用位操作替代遍历,利用二进制特性简化计算

相关推荐
程序员榴莲2 小时前
Java(十二)抽象类
java·开发语言
wanderist.2 小时前
图论模板整理
算法·深度优先·图论
木子欢儿2 小时前
在 Fedora 上配置 Go 语言(Golang)开发环境
开发语言·后端·golang
Patrick_Wilson2 小时前
你的 MR 超过 500 行了吗?——大型代码合并请求拆分实战指南
前端·代码规范·前端工程化
神三元2 小时前
大模型工具调用输出的 JSON,凭什么能保证不出错?
前端·ai编程
超级大只老咪2 小时前
线性递推通用模板
java·开发语言·算法
得物技术2 小时前
基于 Cursor Agent 的流水线 AI CR 实践|得物技术
前端·程序员·全栈
17(无规则自律)2 小时前
DFS:带重复项的全排列,程序运行全流程解析
c++·算法·深度优先
AI棒棒牛2 小时前
SCI核心论文剖析:ICSD-YOLO:面向工业现场安全的实时智能检测算法
算法·yolo·目标检测·计算机视觉·目标跟踪·yolo26