【LeetCode学习计划】《算法-入门-C++》第14天 位运算

LeetCode【学习计划】:【算法】



190. 颠倒二进制位

LeetCode: 190. 颠倒二进制位

简 单 \color{#00AF9B}{简单}

颠倒给定的 32 位无符号整数的二进制位。

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825

示例 1:

输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
     因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000

示例 2:

输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
     因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111

提示:

  • 输入是一个长度为 32 的二进制字符串

进阶: 如果多次调用这个函数,你将如何优化你的算法?


方法1:逐位颠倒

我们在循环i中,设i的初值为31,每次循环i自减直至0。每次循环枚举n的最低位:n & 1;然后将它左移i位与结果变量ans作或操作:ans |= (n & 1) << i,这样这一位就被存放到了结果中。

最后,n向右移位,以便下一次循环中最低位的枚举。

需要注意的是,在某些语言(如Java、JavaScript)中没有无符号整型,因此对n的右移操作要使用逻辑右移。

#include <cstdint>
class Solution
{
public:
    uint32_t reverseBits(uint32_t n)
    {
        uint32_t ans = 0;
        for (int i = 31; i >=0 && n != 0; i--)
        {
            ans |= (n & 1) << i;
            n >>= 1;
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O ( k ) O(k) O(k)。其中,k为C++中int类型的二进制位数减1,int型的长度由编译器决定。

  • 空间复杂度: O ( 1 ) O(1) O(1)

参考结果

Accepted
600/600 cases passed (4 ms)
Your runtime beats 54.64 % of cpp submissions
Your memory usage beats 70.26 % of cpp submissions (5.8 MB)

方法2:分治

题目中为翻转二进制位,其实和翻转一个字符串有一点类似。若要翻转一个字符串,我们可以将其分成左右两个部分,对每个部分进行递归的翻转,最后再将左半部分拼在右半部分的后面就完成了翻转。

由于题目给出n的类型为无符号32位整型uint32_t,它的长度确定为32位,且次方法步骤较少,那么我们就可以把递归的过程给直接写明,而不用写作函数递归调用的方式。

  1. 递归的最低层将每个相邻的二进制位进行交换。
  2. 交换每2个相邻的二进制位
  3. 交换每4个相邻的二进制位
  4. 交换每8个相邻的二进制位
  5. 交换每16个相邻的二进制位

最后,32位的二进制位便被交换完毕。

以最低层为例,观察一下具体操作:

  • 设用于移位的数:

    M1 = 0x55555555; // 01010101010101010101010101010101
    
  • 先右移一位,取出的就是奇数位上的数,并且已经右移过了:

    (n >> 1) & M1
    
  • 取出偶数位上的数,并左移:

    (n & M1) << 1
    
  • 将它们进行或操作便能完成交换:

    n = (n >> 1) & M1 | (n & M1) << 1;
    
#include <cstdint>
class Solution
{
private:
    const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101
    const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011
    const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
    const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111

public:
    uint32_t reverseBits(uint32_t n)
    {
        n = (n >> 1) & M1 | (n & M1) << 1;
        n = (n >> 2) & M2 | (n & M2) << 2;
        n = (n >> 4) & M4 | (n & M4) << 4;
        n = (n >> 8) & M8 | (n & M8) << 8;
        n = n >> 16 | n << 16;
        return n;
    }
};

复杂度分析

  • 时间复杂度: O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( 1 ) O(1) O(1)

参考结果

Accepted
600/600 cases passed (0 ms)
Your runtime beats 100 % of cpp submissions
Your memory usage beats 31.52 % of cpp submissions (5.9 MB)


136. 只出现一次的数字

LeetCode: 136. 只出现一次的数字

简 单 \color{#00AF9B}{简单}

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

示例 2:

输入: [4,1,2,1,2]
输出: 4

方法1:哈希表

这道题用哈希表可以解决,但是太简单了,也没什么意思。


方法2:异或

这一方法我们需要用到异或 ⊕ \oplus 运算的知识。

异或的概念是:若两者对应的二进制位不同,则为1;相同则为0

例:

01010 ⊕ 10101 = = 11111 11111 ⊕ 00000 = = 00000 10 ⊕ 11 = = 10 10 ⊕ 00 = = 01 01010 \oplus 10101 == 11111 \\ 11111 \oplus 00000 == 00000 \\ 10 \oplus 11 == 10 \\ 10 \oplus 00 == 01 0101010101==111111111100000==000001011==101000==01

如果是同一个数进行异或操作,那么每一位都是相同的,则结果必然是0。如果是多个“同一个数”进行异或操作,因为每“同一个数”的异或结果为0,而0==0,因此异或结果也是0。这就表明,只要每个数出现的次数为偶数,则所有数进行异或操作的结果都是0:

a n s = ( a 1 ⊕ a 1 ) ⊕ ( a 2 ⊕ a 2 ) ⊕ . . . ⊕ ( a n ⊕ a n ) = 0 ⊕ 0 ⊕ . . . ⊕ 0 = 0 \begin{aligned} ans = & (a_1 \oplus a_1) \oplus (a_2 \oplus a_2) \oplus ... \oplus (a_n \oplus a_n) \\ & = 0 \oplus 0 \oplus ... \oplus 0 \\ & = 0 \end{aligned} ans=(a1a1)(a2a2)...(anan)=00...0=0

这样一来,只有中间有一个多出来的数,那么结果就是那个数。因为某个数的二进制位中,00的异或结果还是010的异或结果还是1,所以某个数和0的异或结果还是那个数。这样一来,我们就能找到那个单独的数了:

a n s = ( a 1 ⊕ a 1 ) ⊕ ( a 2 ⊕ a 2 ) ⊕ . . . ⊕ ( a n − 1 ⊕ a n − 1 ) ⊕ a n = 0 ⊕ 0 ⊕ . . . ⊕ 0 ⊕ a n = a n \begin{aligned} ans = & (a_1 \oplus a_1) \oplus (a_2 \oplus a_2) \oplus ... \oplus (a_{n-1} \oplus a_{n-1}) \oplus a_n \\ & = 0 \oplus 0 \oplus ... \oplus 0 \oplus a_n \\ & = a_n \end{aligned} ans=(a1a1)(a2a2)...(an1an1)an=00...0an=an

#include <vector>
using namespace std;
class Solution
{
public:
    int singleNumber(vector<int> &nums)
    {
        int ans = 0;
        for (int i = 0; i < nums.size(); i++)
        {
            ans ^= nums[i];
        }
        return ans;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)n为数组的长度,数组中每一个数都要遍历一次。

  • 空间复杂度: O ( 1 ) O(1) O(1)

参考结果

Accepted
61/61 cases passed (16 ms)
Your runtime beats 72.1 % of cpp submissions
Your memory usage beats 92.58 % of cpp submissions (16.4 MB)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

亡心灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值