LeetCode--任务8--位运算

37 篇文章 0 订阅
14 篇文章 0 订阅

位运算技术在求解算法题中的应用

1 C# 和 Python 中的位运算操作

1. 原码、反码和补码

二进制有三种不同的表示形式:原码、反码和补码,++计算机内部使用补码来表示++。

原码:就是其二进制表示(注意,有一位符号位)。

00 00 00 11 -> 3
10 00 00 11 -> -3

反码:正数的反码就是原码,负数的反码是符号位不变,其余位取反(对应正数按位取反)。

00 00 00 11 -> 3
11 11 11 00 -> -3

补码:正数的补码就是原码,负数的补码是反码+1。

00 00 00 11 -> 3
11 11 11 01 -> -3

符号位:最高位为符号位,0表示正数,1表示负数。在位运算中符号位也参与运算。

2. 按位非操作 ~

~ 1 = 0
~ 0 = 1

~num的补码中的 0 和 1 全部取反(0 变为 1,1 变为 0)有符号整数的符号位在 ~ 运算中同样会取反。

00 00 01 01 -> 5
~
---
11 11 10 10 -> -6
​
11 11 10 11 -> -5
~
---
00 00 01 00 -> 4

3. 按位与操作 &

1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

只有两个对应位都为 1 时才为 1

00 00 01 01 -> 5
&
00 00 01 10 -> 6
---
00 00 01 00 -> 4

4. 按位或操作 |

1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0

只要两个对应位中有一个 1 时就为 1

00 00 01 01 -> 5
|
00 00 01 10 -> 6
---
00 00 01 11 -> 7

5. 按位异或操作 ^

1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0

只有两个对应位不同时才为 1

00 00 01 01 -> 5
^
00 00 01 10 -> 6
---
00 00 00 11 -> 3

异或操作的性质:满足交换律和结合律

A: 00 00 11 00
B: 00 00 01 11

A^B: 00 00 10 11
B^A: 00 00 10 11

A^A: 00 00 00 00
A^0: 00 00 11 00

A^B^A: = A^A^B = B = 00 00 01 11

6. 按位左移操作 <<

num << inum的二进制表示向左移动i位所得的值。

00 00 10 11 -> 11
11 << 3
---
01 01 10 00 -> 88 

7. 按位右移操作 >>

num >> inum的二进制表示向右移动i位所得的值。

00 00 10 11 -> 11
11 >> 2
---
00 00 00 10 -> 2 

8. 利用位运算实现快速计算

通过 <<>> 快速计算2的倍数问题。

n << 1 -> 计算 n*2
n >> 1 -> 计算 n/2,负奇数的运算不可用
n << m -> 计算 n*(2^m),即乘以 2 的 m 次方
n >> m -> 计算 n/(2^m),即除以 2 的 m 次方
1 << n -> 2^n

通过 ^ 快速交换两个整数。

a ^= b
b ^= a
a ^= b

通过 a & (-a) 快速获取a的最后为 1 位置的整数。

00 00 01 01 -> 5
&
11 11 10 11 -> -5
---
00 00 00 01 -> 1

00 00 11 10 -> 14
&
11 11 00 10 -> -14
---
00 00 00 10 -> 2

9. 利用位运算实现整数集合

一个数的二进制表示可以看作是一个集合(0 表示不在集合中,1 表示在集合中)。

比如集合 {1, 3, 4, 8},可以表示成 01 00 01 10 10 而对应的位运算也就可以看作是对集合进行的操作。

元素与集合的操作:

a | (1<<i)  -> 把 i 插入到集合中
a & ~(1<<i) -> 把 i 从集合中删除
a & (1<<i)  -> 判断 i 是否属于该集合(零不属于,非零属于)

集合之间的操作:

a 补   -> ~a
a 交 b -> a & b
a 并 b -> a | b
a 差 b -> a & (~b)

 

2 只出现一次的数字

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

说明

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

示例 1:

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

示例 2:

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

思路: 利用"异或"操作的性质。

A: 00 00 11 00
B: 00 00 01 11

A^A: 00 00 00 00
A^0: 00 00 11 00

A^B^A: = A^A^B = B = 00 00 01 11

C# 实现

  • 状态:通过

  • 16 / 16 个通过测试用例

  • 执行用时: 144 ms, 在所有 C# 提交中击败了 91.76% 的用户

  • 内存消耗: 25.4 MB, 在所有 C# 提交中击败了 11.39% 的用户

public class Solution
{
    public int SingleNumber(int[] nums)
    {
        int result = 0;
            
        for (int i = 0; i < nums.Length; i++)
        {
            result ^= nums[i];
        }
        return result;
    }
}

Python 实现

  • 执行结果:通过

  • 执行用时:44 ms, 在所有 Python3 提交中击败了 84.17% 的用户

  • 内存消耗:15.3 MB, 在所有 Python3 提交中击败了 5.26% 的用户

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        result = 0
        for item in nums:
            result ^= item
        return result

3 2的幂

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

示例 1:

输入: 1
输出: true
解释: 2^0 = 1

示例 2:

输入: 16
输出: true
解释: 2^4 = 16

示例 3:

输入: 218
输出: false

思路: 利用"异或"操作的性质。

A: 00 00 11 00

A^A: 00 00 00 00

C# 语言

  • 状态:通过

  • 1108 / 1108 个通过测试用例

  • 执行用时: 36 ms, 在所有 C# 提交中击败了 100.00% 的用户

  • 内存消耗: 14.7 MB, 在所有 C# 提交中击败了 100.00% 的用户

public class Solution
{
    public bool IsPowerOfTwo(int n)
    {
        if (n < 0)
            return false;
        for (int i = 0; i < 32; i++)
        {
            int mask = 1 << i;
            if ((n ^ mask) == 0)
                return true;
        }
        return false;
    }
}

Python 语言

  • 执行结果:通过

  • 执行用时:44 ms, 在所有 Python3 提交中击败了 51.91% 的用户

  • 内存消耗:13.6 MB, 在所有 Python3 提交中击败了 6.25% 的用户

class Solution:
    def isPowerOfTwo(self, n: int) -> bool:
        for i in range(32):
            mask = 1 << i
            if n ^ mask == 0:
                return True
        return False

 

4 只出现一次的数字 III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。找出只出现一次的那两个元素。

示例 :

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

注意:

  1. 结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。

  2. 你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

思路: 利用"异或"操作的性质。

通过异或操作 ^ 去除掉恰好重复出现两次的元素,这时得到两个只出现一次整数的异或结果different

A: 00 00 11 00
B: 00 00 01 11

A^B: 00 00 10 11
B^A: 00 00 10 11

A^A: 00 00 00 00
A^0: 00 00 11 00

A^B^A: = A^A^B = B = 00 00 01 11

获取different二进制中最后一位1,通过该位,可以把这两个数分离出来,这两个数在该位是不同的。也即通过该位把 nums 分成了两组,该位是 1 的一组,该位是 0 的一组,然后求两组中只出现一次的整数。

通过 a & (-a) 快速获取a的最后为 1 位置的整数。

00 00 01 01 -> 5
&
11 11 10 11 -> -5
---
00 00 00 01 -> 1

00 00 11 10 -> 14
&
11 11 00 10 -> -14
---
00 00 00 10 -> 2

C# 语言

  • 执行结果:通过

  • 执行用时:280 ms, 在所有 C# 提交中击败了 83.33% 的用户

  • 内存消耗:31.2 MB, 在所有 C# 提交中击败了 100.00% 的用户

public class Solution
{
    public int[] SingleNumber(int[] nums)
    {
        if (nums.Length < 2)
            return new int[2];
        int different = 0;
        for (int i = 0; i < nums.Length; i++)
        {
            different ^= nums[i];
        }
        different &= -1 * different;
        int num1 = 0, num2 = 0;
        for (int i = 0; i < nums.Length; i++)
        {
            if ((different & nums[i]) == 0)
            {
                num1 ^= nums[i];
            }
            else
            {
                num2 ^= nums[i];
            }
        }
        return new int[] { num1, num2 };
    }
}

Python 语言

  • 执行结果:通过

  • 执行用时:44 ms, 在所有 Python3 提交中击败了 83.70% 的用户

  • 内存消耗:14.8 MB, 在所有 Python3 提交中击败了 33.33% 的用户

class Solution:
    def singleNumber(self, nums: List[int]) -> List[int]:
        if len(nums) < 2:
            return []
        different = 0
        for num in nums:
            different ^= num
        different &= -1 * different
        num1, num2 = 0, 0
        for num in nums:
            if (num & different) == 0:
                num1 ^= num
            else:
                num2 ^= num
        return [num1, num2]

 

5 子集

给定一组 不含重复元素 的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

思路: 利用整数集合的思路。

{1,2,3}为例,三个数,共2^3个子集。

000 -> []
100 -> [1]
101 -> [1,3]
110 -> [1,2]
111 -> [1,2,3]
...

C# 语言

  • 状态:通过

  • 10 / 10 个通过测试用例

  • 执行用时: 348 ms, 在所有 C# 提交中击败了 97.80% 的用户

  • 内存消耗: 29.5 MB, 在所有 C# 提交中击败了 6.67% 的用户

public class Solution
{
    public IList<IList<int>> Subsets(int[] nums)
    {
        IList<IList<int>> result = new List<IList<int>>();
        int count = nums.Length;

        for (int i = 0; i < 1 << count; i++)
        {
            IList<int> item = new List<int>();
            for (int j = 0; j < count; j++)
            {
                int mask = 1 << j;
                if ((mask & i) != 0)
                    item.Add(nums[j]);
            }
            result.Add(item);
        }
        return result;
    }
}

Python 语言

  • 执行结果:通过

  • 执行用时:40 ms, 在所有 Python3 提交中击败了 63.08% 的用户

  • 内存消耗:13.8 MB, 在所有 Python3 提交中击败了 5.72% 的用户

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        count = len(nums)
        result = []
        for i in range(1 << count):
            item = []
            for j in range(count):
                mask = 1 << j
                if (mask & i) != 0:
                    item.append(nums[j])
            result.append(item)
        return result

6 Pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000

示例 2:

输入: 2.10000, 3
输出: 9.26100

示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

示例 4:

输入: 1.00000, -2147483648
输出: 1.00000

说明:

  • -100.0 < x < 100.0

  • n 是 32 位有符号整数,其数值范围是 [−2^31, 2^31 − 1] 。

思路:利用快速幂法。

假设我们要求a^b,那么b可以拆成二进制表示,例如当b = 5时,5的二进制是0101,5 = 2^3×0 + 2^2×1 + 2^1×0 + 2^0×1,因此,我们将a^5转化为算 a^(2^3×0 + 2^2×1 + 2^1×0 + 2^0×1),即a^(2^0) × a^(2^2)

我们先算出所有2的幂,然后在算出所有x的2的幂次方。再把n拆成二进制,把二进制当中对应位置是1的值乘起来,就得到了结果。这套方法称为 快速幂法

C# 实现

  • 执行结果:通过

  • 执行用时:56 ms, 在所有 C# 提交中击败了 51.87% 的用户

  • 内存消耗:15.1 MB, 在所有 C# 提交中击败了 50.00% 的用户

public class Solution
{
    public double MyPow(double x, int n)
    {
        int neg = n < 0 ? -1 : 1;
        long g = Math.Abs((long)n);

        double[] d = new double[32];
        d[0] = x;
        for (int i = 1; i < 32; i++)
        {
            d[i] = d[i - 1] * d[i - 1];
        }

        double result = 1.0d;
        for (int i = 0; i < 32; i++)
        {
            int mask = 1 << i;
            if ((mask & g) != 0)
            {
                result *= d[i];
            }
        }
        return neg != -1 ? result : 1.0 / result;
    }
}

注意:++long g = Math.Abs((long)n);需要把n转换成long,因为Math.Abs(int.MinValue)会产生溢出错误++。

Python 实现

  • 执行结果:通过

  • 执行用时:36 ms, 在所有 Python3 提交中击败了 75.02% 的用户

  • 内存消耗:13.8 MB, 在所有 Python3 提交中击败了 8.33% 的用户

class Solution:
    def myPow(self, x: float, n: int) -> float:
        neg = -1 if n < 0 else 1
        n = abs(n)
        d = list()
        d.append(x)
        for i in range(1, 32):
            d.append(d[-1] * d[-1])
        result = 1.0
        for i in range(32):
            mask = 1 << i
            if (mask & n) != 0:
                result *= d[i]
        return result if neg != -1 else 1.0 / result

7 只出现一次的数字 II

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

说明:

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

示例 1:

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

示例 2:

输入: [0,1,0,1,0,1,99]
输出: 99

思路:

初始result = 0,将每个数想象成 32 位的二进制,对于每一位的二进制的1累加起来必然是3N或者3N + 1(出现3次和1次);3N代表目标值在这一位没贡献,3N + 1代表目标值在这一位有贡献(=1),然后将所有有贡献的位记录到result中。这样做的好处是如果题目改成k个一样,只需要把代码改成count % k即可,很通用并列去找每一位。

C# 语言

  • 执行结果:通过

  • 执行用时:112 ms, 在所有 C# 提交中击败了 91.53% 的用户

  • 内存消耗:25.2 MB, 在所有 C# 提交中击败了 100.00% 的用户

public class Solution
{
    public int SingleNumber(int[] nums)
    {
        int result = 0;
        for (int i = 0; i < 32; i++)
        {
            int mask = 1 << i;
            int count = 0;
            for (int j = 0; j < nums.Length; j++)
            {
                if ((nums[j] & mask) != 0)
                {
                    count++; 
                }
            }
            if (count % 3 != 0)
            {
                result |= mask;
            }
        }
        return result;
    }
}

Python 语言

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        result = 0
        for i in range(32):
            mask = 1 << i
            count = 0
            for num in nums:
                if num & mask != 0:
                    count += 1
            if count % 3 != 0:
                result |= mask
        return result

以上 Python 代码与 C# 代码逻辑完全一致,但提交时报错,错误信息如下:

输入:[-2,-2,1,1,-3,1,-3,-3,-4,-2]
输出:4294967292
预期结果:-4

我们发现:

-4 补码为 1111 1111 1111 1111 1111 1111 1111 1100

如果不考虑符号位

1111 1111 1111 1111 1111 1111 1111 1100 -> 4294967292 

是不是很坑,C++,C#,Java等语言的整型是限制长度的,如:byte 8位,int 32位,long 64位,但 Python 的整型是不限制长度的(即不存在高位溢出),所以,当输出是负数的时候,会导致认为是正数!因为它把32位有符号整型认为成了无符号整型,真是坑。

我们对以上的代码进行修改,加入判断条件 if result > 2 ** 31-1: 超过32位整型的范围就表示负数了result -= 2 ** 32,即可得到对应的负数。

  • 执行结果:通过

  • 执行用时:96 ms, 在所有 Python3 提交中击败了 19.00% 的用户

  • 内存消耗:14.8 MB, 在所有 Python3 提交中击败了 25.00% 的用户

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        result = 0
        for i in range(32):
            mask = 1 << i
            count = 0
            for num in nums:
                if num & mask != 0:
                    count += 1
            if count % 3 != 0:
                result |= mask
            if result > 2 ** 31-1:
                result -= 2 ** 32
        return result

 

8 格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。

给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。

示例 1:

输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2

对于给定的 n,其格雷编码序列并不唯一。
例如,[0,2,3,1] 也是一个有效的格雷编码序列。

00 - 0
10 - 2
11 - 3
01 - 1

示例 2:

输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。
    给定编码总位数为 n 的格雷编码序列,其长度为 2^n。
    当 n = 0 时,长度为 2^0 = 1。
    因此,当 n = 0 时,其格雷编码序列为 [0]。

思路:

雷格码

由 n 位推导 n+1 位结果时,n+1 位结果包含 n 位结果,同时包含 n 位结果中在高位再增加一个位 1 所形成的令一半结果,但是这一半结果需要与前一半结果镜像排列。

C# 语言

  • 状态:通过

  • 12 / 12 个通过测试用例

  • 执行用时: 296 ms, 在所有 C# 提交中击败了 95.83% 的用户

  • 内存消耗: 24.8 MB, 在所有 C# 提交中击败了 16.67% 的用户

public class Solution
{
    public IList<int> GrayCode(int n)
    {
        IList<int> lst = new List<int>();
        lst.Add(0);
        for (int i = 1; i <= n; i++)
        {
            for (int j = lst.Count - 1; j >= 0; j--)
            {
                int item = lst[j] + (1 << i - 1);
                lst.Add(item);
            }
        }
        return lst;
    }
}

Python 语言

  • 执行结果:通过

  • 执行用时:44 ms, 在所有 Python3 提交中击败了 45.92% 的用户

  • 内存消耗:13.8 MB, 在所有 Python3 提交中击败了 20.00% 的用户

class Solution:
    def grayCode(self, n: int) -> List[int]:
        lst = [0]
        for i in range(1, n + 1):
            count = len(lst)
            for j in range(count - 1, -1, -1):
                lst.append(lst[j] + (1 << i - 1))
        return lst

注意:运算符的优先级

  • 一元运算符优于二元运算符。如正负号。

  • 先算术运算,后移位运算,最后位运算。例如 1 << 3 + 2 & 7等价于 (1 << (3 + 2)) & 7

  • 逻辑运算最后结合

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值