给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
加粗样式
示例 1:
输入: [5,7]
输出: 4
示例 2:
输入: [0,1]
输出: 0
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/
暴力解法:
按题目意思把所有数按位与可以很容易得到以下程序:
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
int ans=m;
for(int i=m+1;i<=n;i++){
ans=ans&i;
}
return ans;
}
};
因为一般 O(n)时间复杂度复杂度能解决的问题对应循环次数一般在1e8以内 ,所以这个解法必然会因为超时而行不通。
官网题解思路:
思路:[m,n]区间里元素的全都按位与就等同于m和n表示成二进制以后的公共前缀对应的数值。这个思路估计大部分人都想不到(我也是QWQ),刚看到的时候肯定也是很懵。我姑且就来尝试说一下这个二进制的公共前缀对应的数值为什么就是答案。
为什么对:
首先通过下面的4个描述建立一个较直观的感受(描述的位基于数的二进制):
1.按位与运算的一个特点是如果这一位上有一个0出现,那这一位按位与的结果必然是0。
2.m和m+1的最后一位一个是1,另一个是0。按位与的结果最后一位必然为0。
3.m在不断加1的过程中如果产生了进位,把m和这些不断加1后的数按位与,那么进位(包括进位)之后的数都会为变成0。
4.由于[m,n]区间中的数是连续的整数,在区间跨度不是很大,进位没有很多的情况下,前面的一部分数都一样,即存在公共前缀。
为什么对:假设m在不断加1并产生一定数量的进位,并最终到n的过程中,最高位进位的位置是第i位。那么中间过程必然存在一个x值的i-1位为011111…1,而(x+1)即恰好产生进位的数为100000…0。按位与就会让后面的i位全为0,即便(x+1)再加1之后还有进位,只要不到(i+1),按位与的结果就不会变化。
正解一:
把m和n同时右移,两者相等时的m(或n)就是前缀,再左移回来。
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
int move=0;
while(m!=n){ //把m,n右移直到相等
m=m>>1;
n=n>>1;
move++; //move记录右移的次数
}
return m<<move; //把前缀左移回来
}
};
正解二
由上面的解法可以发现,把前缀后面的数全部置为0也能达到目的。 引入Brian Kernighan 算法:n&(n-1)可以实现把n的二进制数最低位的1置为0的目的。如下图,当n=12时,12&(12-1)就是把12的最低位即第3位的1变为0的结果。
基于前缀和的思想,把较大数n不断去掉最后一位1直到n<=m就得到答案:
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
while (m < n) { / / 不断去掉 n的最后一位1直到n<=m
n = n & (n - 1);
}
return n;
}
};