Leetcode201. 数字范围按位与

Every day a Leetcode

题目来源:201. 数字范围按位与

最直观的解决方案就是迭代范围内的每个数字,依次执行按位与运算,得到最终的结果,但此方法在 [left, right] 范围较大的测试用例中会因超出时间限制而无法通过,因此我们需要另寻他路。

我们观察按位与运算的性质。对于一系列的位,只要有一个零值的位,那么这一系列位的按位与运算结果都将为零。

对所有数字执行按位与运算的结果是所有对应二进制字符串的公共前缀再用零补上后面的剩余位。

这个结果实际上是这些数的二进制表示的最长公共前缀,后面补零。

解法1:位移

鉴于上述问题的陈述,我们的目的是求出两个给定数字的二进制字符串的公共前缀,这里给出的第一个方法是采用位移操作。

我们的想法是将两个数字不断向右移动,直到数字相等,即数字被缩减为它们的公共前缀。然后,通过将公共前缀向左移动,将零添加到公共前缀的右边以获得最终结果。

代码:

/*
 * @lc app=leetcode.cn id=201 lang=cpp
 *
 * [201] 数字范围按位与
 */

// @lc code=start
class Solution
{
public:
    int rangeBitwiseAnd(int left, int right)
    {
        int shift = 0;
        // 找到公共前缀
        while (left < right)
        {
            left >>= 1;
            right >>= 1;
            shift++;
        }
        return left << shift;
    }
};
// @lc code=end

结果:

在这里插入图片描述

复杂度分析:

时间复杂度:O(log(right)),算法的时间复杂度取决于 left 和 right 的二进制位数,由于 right 更大,因此时间复杂度取决于 right 的二进制位数。

空间复杂度:O(1)。我们只需要常数空间存放若干变量。

解法2:Brian Kernighan 算法

还有一个位移相关的算法叫做「Brian Kernighan 算法」,它用于清除二进制串中最右边的 1。

「Brian Kernighan 算法」的关键在于我们每次对 number 和 number − 1 之间进行按位与运算后,number 中最右边的 1 会被抹去变成 0。

基于上述技巧,我们可以用它来计算两个二进制字符串的公共前缀。

其思想是,对于给定的范围 [left, right],我们可以对数字 right 迭代地应用上述技巧,清除最右边的 1,直到它小于或等于 left,此时非公共前缀部分的 1 均被消去。因此最后我们返回 right 即可。

代码:

class Solution
{
public:
    int rangeBitwiseAnd(int left, int right)
    {
        while (left < right)
        {
            // 抹去最右边的 1
            right = right & (right - 1);
        }
        return right;
    }
};

结果:

在这里插入图片描述

复杂度分析:

时间复杂度:O(log(right)),和位移方法类似,算法的时间复杂度取决于 left 和 right 二进制展开的位数。尽管和位移方法具有相同的渐近复杂度,但「Brian Kernighan 算法」需要的迭代次数会更少,因为它跳过了两个数字之间的所有零位。

空间复杂度:O(1)。我们只需要常数空间存放若干变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值