Bitwise AND of Numbers Range - 题解与思路

题目链接:Bitwise AND of Numbers Rangeleetcode

题目与直觉理解

题目:给定两个整数 left 和 right,表示闭区间 [left, right],返回区间内所有整数的按位与结果。leetcode

约束:0 <= left <= right <= 2^31 - 1,也就是 32 位非负整数范围。leetcode

直觉上,如果区间很大(比如从 1 到一个非常大的数),那么某些二进制位在中间某个数上一定会变成 0,最后按位与结果也会在这些位上变成 0。唯一可能保持为 1 的,只能是整个区间里从头到尾都从未变化过的那部分"高位前缀"。

初始错误思路:只看"最高位 1"

一开始容易想到的一个思路是:

把每个数都看成 32 位整数,找"最高位的 1 在哪一位"。

如果区间内所有数的最高位 1 都在同一位,就认为结果是"只有这一位是 1,其余位为 0";否则返回 0。

这个思路抓住了"高位"这个感觉,但有两个致命问题:

只看"最高位是否一致",忽略了"更低位可能也一直为 1"。例如区间 [6, 7]:

6 = 110,7 = 111,最高位 1 都在同一位,但真实结果是 110 (6),而不是 100 (4)。

没有真正找到"公共前缀"的全部部分,等于只保留了最左边那一位。

这说明:我们需要的不是"最高位",而是 left 和 right 的完整"二进制公共前缀"。

正确核心:找二进制公共前缀

这道题有一个非常典型的结论:

区间 [left, right] 所有数的按位与结果,就是 left 和 right 在二进制下的 公共前缀,其余低位全部变为 0。leetcode

原因可以这样理解:

从 left 增加到 right 的过程中,只要某一位在这个过程中经历过从 0 变 1 或从 1 变 0 的变化,那么这一位在某个数上必然是 0,按位与之后这个位就会变 0。

只有在 left 和 right 对应位完全一致、且在整个区间内不发生变化的那一段"高位前缀",才能在结果中保持为 1。

所以问题变成:如何高效地求出 left 和 right 的公共前缀?

经典解法:不断右移,直到相等

一种常见且优雅的做法是:

用一个计数 shift = 0 记录右移的次数。

当 left < right 时,同时右移 left 和 right 一位(left >>= 1; right >>= 1;),并让 shift++。

循环直到 left == right。此时的 left(或 right)就是它们的公共前缀。

最后将这个公共前缀左移 shift 位,把低位补回 0:left << shift,即为答案。leetcode

示意伪代码(C 风格):

cpp

int rangeBitwiseAnd(int left, int right) {

int shift = 0;

while (left < right) {

left >>= 1;

right >>= 1;

++shift;

}

return left << shift;

}

这里用 while (left < right) 和 while (left != right) 在题目保证 left <= right 的前提下是等价的,选择哪个更多是风格问题。

为什么右移有效:从"丢掉一定为 0 的尾部"看

每次右移一位,在本质上是在做:丢掉当前的最低位。

当某一位开始在 left 与 right 之间发生差异时,说明在区间 [left, right] 内,这一位必然经历了 0/1 的变化。

因为按位与对所有数都要是 1 才能为 1,一旦某一位在区间里出现过 0,最终结果这位一定是 0。

所以,只要 left < right,说明仍有某些低位是不稳定的(在中间会变化),这些低位我们干脆整体右移"扔掉"。

当右移到 left == right 的那一刻,说明剩下的所有位在整个区间内已经完全一致,它们就是公共前缀。

最后再通过左移 shift 位,把这些高位放回原来的位置,低位补 0,得到最终按位与结果。

细节问题:while 条件与移位方向

关于你在过程中的几个思考点,顺便也整理一下:

left != right 还是 left < right?

题目保证 left <= right,输入不会出现 left > right。leetcode

又因为每一步都同时右移 left 和 right,不会人为制造 left > right 的情况。

所以 while (left < right) 和 while (left != right) 在这题上效果等价,用哪一个主要看你觉得哪一种更清晰。

算术右移还是逻辑右移?

左移只有一个操作符 <<,没有算术/逻辑之分。

右移在 C/C++/Java 中,>> 对有符号整数通常是"算术右移",保留符号位;Java 还有 >>> 是逻辑右移。

本题保证 0 <= left, right <= 2^31 - 1,全部是非负数,即最高位为 0,不会出现符号扩展问题,所以用 >> 即可,算术右移和逻辑右移在这里表现一致。leetcode

left == 0 需要特判吗?

不需要。left == 0 时,区间 [0, right] 一定包含 0,整个区间按位与结果必然为 0。

在上面的循环中:

left 右移始终为 0。

right 不断右移直到也变为 0,此时 left == right == 0。

最终返回 0 << shift,仍然是 0,符合预期,无需额外分支。

示例推演:验证思路

以几个典型例子推一遍这个过程,可以更有信心:

left = 6 (110),right = 7 (111):

第一次:left = 3 (11),right = 3 (11),shift = 1。

循环结束,返回 3 << 1 = 110 (6),与真实按位与结果一致。

left = 12 (1100),right = 15 (1111):

第一次:1100 -> 110,1111 -> 111,shift = 1。

第二次:110 -> 11,111 -> 11,shift = 2。

返回 3 << 2 = 1100 (12),与逐个按位与的结果一致。

left = 1,right = 2147483647:

不断右移后,最终两者都会变成 0,shift 为 31。

返回 0 << 31 = 0,符合题目示例。

总结:适合写成博客的结构建议

如果写成一篇博客,大致可以按下面结构组织:

题目描述与样例

直观思路:为什么要找公共前缀

错误/不完整思路:只看最高位 1 的陷阱

正确解法一:右移找公共前缀(你现在实现的解法)

思路描述

代码实现

示例推演

边界条件讨论(left == 0、移位类型等)

复杂度分析:时间 O(logN),空间 O(1)

小结:这类题的共性------"范围按位运算"往往等价于"端点的公共前缀"
https://leetcode.com/problems/bitwise-and-of-numbers-range/?envType=study-plan-v2&envId=top-interview-150
https://leetcode.com/problems/bitwise-and-of-numbers-range/submissions/1876552823/?envType=study-plan-v2&envId=top-interview-150

相关推荐
weixin_499771555 分钟前
C++中的组合模式
开发语言·c++·算法
iAkuya37 分钟前
(leetcode)力扣100 62N皇后问题 (普通回溯(使用set存储),位运算回溯)
算法·leetcode·职场和发展
近津薪荼37 分钟前
dfs专题5——(二叉搜索树中第 K 小的元素)
c++·学习·算法·深度优先
xiaoye-duck39 分钟前
吃透 C++ STL list:从基础使用到特性对比,解锁链表容器高效用法
c++·算法·stl
松☆42 分钟前
CANN与大模型推理:在边缘端高效运行7B参数语言模型的实践指南
人工智能·算法·语言模型
java干货1 小时前
为什么 “File 10“ 排在 “File 2“ 前面?解决文件名排序的终极算法:自然排序
开发语言·python·算法
皮皮哎哟1 小时前
数据结构:嵌入式常用排序与查找算法精讲
数据结构·算法·排序算法·二分查找·快速排序
程序员清洒1 小时前
CANN模型剪枝:从敏感度感知到硬件稀疏加速的全链路压缩实战
算法·机器学习·剪枝
vortex51 小时前
几种 dump hash 方式对比分析
算法·哈希算法
Wei&Yan2 小时前
数据结构——顺序表(静/动态代码实现)
数据结构·c++·算法·visual studio code