leetcode中的关于位运算数学题

478 阅读5分钟

前言

这是我参与新手入门的第2篇文章。

电脑执行位运算效率高,平时代码写位运算可以装b,比如i除以2可以写i>>1;判断i是否是奇数可以用if(i&1),同理计算i%4可以直接写i&3.本文分享leetcode中关于位运算数学题。

目录

题1,阶乘

leetcode 50.pow(x,n) 点击展开

题目:实现 pow(x, n) ,即计算 x 的 n 次幂函数。
思路:分治法,总共要实现n个x相乘。将转化二进制数。比如77 的二进制表示 1001101,对应着77=1+4+8+64.即20,22,23,26.于是有,x77=x * x4 * x8 * x64.时间复杂度logN。
代码:

class Solution:
    def myPow(self, x: float, n: int) -> float:
        m = -n if n < 0 else n
        y = 1
        while m:
            if m & 1:
                y *= x
            x *= x
            m >>= 1
        return y if n >= 0 else 1/y

题2,二进制加法

leetcode67.二进制求和 点击展开

题目:给你两个二进制字符串,返回它们的和(用二进制表示)。 输入为 非空 字符串且只包含数字 1 和 0。

解法与思路:1遍历字符串,从个位开始相加,考虑进位;
2内置函数直接return '{:b}'.format(int(a, 2) + int(b, 2));
3利用位运算,不用加减乘除。利用异或和与,实现真正的二进制加法。

解法三代码:

//先不考虑进位,0位与1位相加等于11位与1位相加和0位与0位相加均为1,对应异或运算
//两个位均为1时需要进位,对应与预算
class Solution:
    def addBinary(self, a, b) -> str:
        x, y = int(a, 2), int(b, 2)
        while y:
            answer = x ^ y  # 第一步不考虑进位情况下两数相加的结果
            carry = (x & y) << 1  # 需要进位的地方,左移一位正好等于进位
            x, y = answer, carry  # 重复,第一步的结果加上进位的值
        return bin(x)[2:]       

题3,格雷编码

89.格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。 给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。 示例 输入: 2 输出: [0,1,3,2] 解释:
00 - 0
01 - 1
11 - 3
10 - 2

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

//法一 循环,每一轮在各数的左边加位1
class Solution:
    def grayCode(self, n: int) -> List[int]:
        res=[0]
        for i in range(n):
            offset = 1 << i
            res+=[offset+x for x in res[::-1]]
        return res          

法二,递归,找到规律,每次改变x的一个位置

var grayCode = function(n) {
    var x = 0;
    var res=[];
    core(n, x);
    return res;
    
    function core(n){
        res.push(x);
        for(let i=1; i<=n; i++){
            x^=(1<<(i-1));
            core(i-1, x);
        }
    }
};
//法三数学公式生成
var grayCode = function(n) {
    var ans = new Array();
    for(let i=0;i<(1<<n);i++){
        ans.push(i^(i>>1));
        }
    return ans;     
};

题4,5,7,寻找 出现次数1次的数字

136,137,260只出现一次的数字

题136:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
分析:要求不使用额外空间,则不能存储已出现过的数,还有线性复杂度限制。只能使用数学方法,异或操作,相同的位置会抵消为0,最后的数字便是只出现一次的那个。
代码:

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

题137:其他数字出现3次,找到出现一次的数

分析:与上题类似,需要设计一个方法,让出现3次的位变成0。需要2个数来记录,过程见代码

代码:

class Solution:  
    def singleNumber(self, nums: List[int]) -> int:
        ones = twos = 0;  # ones数字的每一位为1代表该位上出现1次,twos代表2次,
        for i in nums:  # 每一个位不可同在ones和twos中为1,要么出现1次要么2次
            ones = (ones ^ i) & ~twos;    
            twos = (twos ^ i) & ~ones; 
        return ones 
public int singleNumber(int[] nums) {  
        int one = 0, two = 0, three;
        for (int num : nums) {
            // two的相应的位等于1,表示该位出现2次
            two |= (one & num);
            // one的相应的位等于1,表示该位出现1次
            one ^= num;
            // three的相应的位等于1,表示该位出现3次
            three = (one & two);
            // 如果相应的位出现3次,则该位重置为0
            two &= ~three;
            one &= ~three;
        }
        return one;
    }

题260:有2个数出现一次,其他数出现2次

分析:也要求线性复杂度,同理使用异或运算,会等到要求的2数a 和b的异或结果num。对于num中的位1,要么来自与a,要么来自与b中。使用num&(-num)找到最后num中最后一个位1,例如num=0b00110100,负数补码表示,为正数的反码加1,有-num = 0b11001100,num&(-num) = 0b100. 于是再次遍历数组,将数中左起第三位为1的数相异或,便能等到a或b,另一个数则通过异或num得知。

代码:

class Solution:  
    def singleNumber(self, nums: List[int]) -> List[int]:
        num=0
        for x in nums:
            num^=x
        dif = num&(-num) //找到最左边的位1
        res=0
        for y in nums:
            if dif&y:
                res^=y
        return [res,num^res]

小结

其实很多题都是数学题,位运算就是很神奇,计算机作位运算效率很高。

题解代码并非原创,作者收集整理罢了。还有部分内容没写,暂时分享到这。