题目
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
示例 1:
输入: [5,7]
输出: 4
示例 2:
输入: [0,1]
输出: 0
解题思路
暴力版,从m到n一路按位与下去,会超时。
Period Rule
Note the 1s and 0s are changing following a rule: the number of 1s and 0s in digit i
are 2^i
, so for digit i
, there will only be 2^i / 2
1s and 2^i / 2
0s. If the number between left
and right
is larger than 2^i / 2
, then there must be 0s, so after AND the digit must be 0. If the number is smaller than 2^i / 2
, then the result depends on left
and right
. If left
and right
at the current digit are both 0, then it must be 0. If one of them is 0
, then the result is 0. Only when left
and right
both have 1 on the digit, will the result be 1.
Time complexity:
o
(
log
n
)
o(\log n)
o(logn)
Space complexity:
o
(
1
)
o(1)
o(1)
官方题解版
以计算[9, 12]
为例,先写出来每个数字对应的二进制形式:
9: 0 0 0 0 1 0 0 1
10: 0 0 0 0 1 0 1 0
11: 0 0 0 0 1 0 1 1
12: 0 0 0 0 1 1 0 0
因为是从m
到n
的连续区间,所以必然有某个位x满足:该数字的x位为0,x后面的位都是1,下一个数字的x位是1,x后面的数字都是0
这样按位与之后,会导致x及x之后所有位都是0
因此最终的结果实际上是求区间开头和区间结尾的二进制表示中的共同前缀。
求二进制共同前缀的方法:
位移法
将两个数字同时向右移位,并记录移位的次数。当两个数字相等时,就得到了共同的前缀,再将这个共同前缀向左移位移回原先大小即可。
时间复杂度 o ( log 2 n ) o(\log_2n) o(log2n),其中n是区间大小
Brian Kernighan 算法
背景知识:将一个数n
和n - 1
按位与之后,可以去除n
最右边的1
考虑对于区间右边界n
,不断去除最右边的1,当n <= m
时,非公共前缀的1就全部去掉了,此时的n
就是答案
时间复杂度仍然是 o ( log 2 n ) o(\log_2n) o(log2n)
代码
Period Rule
class Solution:
def rangeBitwiseAnd(self, left: int, right: int) -> int:
number_between = right - left + 1
res = 0
period_len = 1
while period_len <= right:
if number_between <= period_len:
res += left & right & period_len
period_len *= 2
return res
位移版
class Solution:
def rangeBitwiseAnd(self, m: int, n: int) -> int:
shift = 0
while m < n:
m >>= 1
n >>= 1
shift += 1
return m << shift
Brian Kernighan 算法
class Solution:
def rangeBitwiseAnd(self, m: int, n: int) -> int:
while n > m:
n &= (n - 1)
return n