Python 1-20 bytes

计算机内部是如何用二进制表示整数的?

计算机内用定点数表示,有 3 种表示法:原码、反码和补码。

原码(true form)是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为 0,负数该位为 1(0 有两种表示:+0 和 -0),其余位表示数值的大小。

一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补 1 就是这个负数的原码。

3 的原码是 11B(B 表示二进制位),在 32 为机器上占 4 个字节,所以高位补 0 就是:
00000000 00000000 00000000 00000011 # 一个字节 8 个 bit 位

3 的绝对值的二进制位就是11B 展开后最高位补1就是:
10000000 00000000 00000000 00000011

原码中的 0 分为 +0 和 -0。不仅如此,在进行不同符号的加法运算或者同符号的减法运算时,不能直接判断出结果的正负,必须要将两个值的绝对值进行比较。然后再进行加减操作。最后符号由绝对值大的决定,于是乎,下面有请反码登场。

反码是数值存储的一种,多应用于系统环境设置,如 linux 平台的目录和文件的默认权限的设置 umask,就是使用反码原理。

正数的反码就是原码,负数的反码等于原码除符号位以外所有位取反。

3 的反码是:
00000000 00000000 00000000 00000011 # 正数的反码就是原码

-3 的反码是:负数的反码等于原码除符号位以外所有位取反!
10000000 00000000 00000000 00000011 # -3 的原码
11111111 11111111 11111111 11111100 # 最高位为符号位,不变,其余取反

反码解决了加减法运算问题,处理 +0 和 -0 的问题,有请补码上台领奖!

**在计算机系统中,数值一律用补码来表示和存储。**原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路支持。

正数的补码与原码相同,负数的补码为其原码除符号位外所有位取反(这就是反码了),然后最低位加1。

3 的补码是:
00000000 00000000 00000000 00000011 # 正数的补码与原码一致

-3 的补码:
10000000 00000000 00000000 00000011 # -3 的原码
11111111 11111111 11111111 11111100 # 负数的补码为其原码除符号位外所有位取反
11111111 11111111 11111111 11111101 # 然后最低位加 1

原、反、补码小结:

正数的反码和补码都与原码相同
负数的反码为该数的原码除符号位外所有位取反
负数的补码为该数的原码除符号位外所有位取反,然后最低位加 1

原码最好理解,但是存在加减法运算不方便的问题;
反码稍微难点,但仅解决了加减法的问题;
补码理解相对困难,但解决了上面的俩问题

位运算符

符号

描述

运算规则

&


两个位都为 1 时,结果才为 1 (统计奇数)全 1 为 1

|


两个位都为 0 时,结果才为 0 (统计偶数)全 0 为 0

^

异或

两个位相同为 0,相异为 1 (常用统计不相同数) 不同为 1

~

取反

0 变 1,1 变 0

<<

左移

各二进位全部左移若干位,高位丢弃,低位补 0

>>

右移

各二进位全部右移若干位,对无符号数,高位补 0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补 0(逻辑右移)

Java 语言运算符优先级

~ + >> > & ^ | 反加移 大于 与异或 与 python 不同 ~ + >> & ^ | > 反加移 与异或 大于

Python 位运算_补码

一、Python 与 &

1. 判断奇偶

二进制数以 1 结尾是奇数,以 0 结尾是偶数。

for i in range(10):  
    if i & 1 == 1: # if i & 1: 
        print(i)

2. 移除 n「二进制表示中最低位」1

n & (n - 1) # 向最后一个 1 借位
bin(6) = '0b110'
bin(5) = '0b101'
bin(4) = '0b100'
6 & 5 = 4 # 将 6 的二进制 最后一位 1 给消除(从左往右);

如果 n 是正整数并且 n & (n - 1) = 0,那么 n 是 2 的幂。

3. 获取 n「二进制表示的最低位」1

n & (-n)
n & ~n + 1

如果 n 是正整数并且 n & -n = n,那么 n 就是 2 的幂。
找出一个二进制数的最右端的第一个 1:一个数取反 + 1

void findFirstRightOne(unsigned int num){
    /*一个数取反 + 1 
    * 如:  00101011000
    * 取反:11010100111
    * 加 1:11010101000 
    * 则其与原数相比,其第一个 1 右边的所有数都为 0,左边的数都相反
    * 此时将他们相与,则只有第一个 1 的位置的结果为 1
    */
    unsigned int rFirst = num & ~num + 1;
    printf("%d 的最右端第一个 1 的十进制数是 %d \n", num, rFirst);
}

2438. 二的幂数组中查询范围内的乘积

给你一个正整数 n ,你需要找到一个下标从 0 开始的数组 powers ,它包含 最少 数目的 2 的幂,且它们的和为 n

MOD = 10 ** 9 + 7
class Solution:
    def productQueries(self, n: int, queries: List[List[int]]) -> List[int]:

        a = [] # powers 含 最少 数目的 2 的幂
        while n:
            lb = n & -n # 获取 n「二进制表示的最低位」1
            a.append(lb)
            n ^= lb
        
        m = len(a)
        res = [[0] * m for _ in range(m)] # [l, r] 的答案
        
        ans = []
        for l, r in queries:
            if res[l][r] == 0:
                tmp = 1
                for i in range(l, r + 1):
                    tmp *= a[i] 
                res[l][r] = tmp % MOD
            ans.append(res[l][r])
        return ans
        
        # return [a[r] if r == l else reduce(lambda x, y: x * y % MOD, a[l: r + 1]) for l, r in queries]

        # na = len(a)
        # res = [[0] * na for _ in range(na)]
        # for i, x in enumerate(a):
        #     res[i][i] = x
        #     for j in range(i + 1, na):
        #         res[i][j] = res[i][j - 1] * a[j] % MOD
        # return [res[l][r] for l, r in queries]

二、Python 异或 ^

异或操作也叫半加运算,其运算法则相当于不带进位的二进制加法;
XOR 在英文里面的定义为 either one (is one), but not both;

异或运算具有以下性质:

  • 异或运算满足交换律和结合律;
  • 任意整数和自身做异或运算的结果都等于 0,即 x ^ x = 0;
  • 任意整数和 0 做异或运算的结果都等于其自身,即 x ^ 0 = 0 ^ x = x。
  • 如果 x = y ^ z 则 x ^ z = (y ^ z) ^ z = y
a ^ a = 0
a ^ 0 = a # 0 和谁异或就是谁
a ^ b ^ c = a ^ c ^ b
a & (-a) = 从右到左 出现第一个 1 和 右边的 0 组成的 数; 
flag = (-a) & (a);

剑指 Offer 65. 不用加减乘除做加法

使用加法器的原理(二进制加法),用代码模拟出十进制的加法运算。

class Solution {
    public int add(int a, int b) {
        while(b != 0){
            int tmp = a;
            a = a ^ b; // 无进位和 sum
            b = (tmp & b) << 1; // 进位 carry
        }
        return a;
    }
}

136. 只出现一次的数字

方法一: 异或

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        # return reduce(lambda x, y: x ^ y, nums)
        # return sum(set(nums))*2-sum(nums)        
        res = 0
        for i in nums: res ^= i  

        return res

方法二:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        d = {}
        for x in nums:
            if x in d: d[x] += 1
            else: d[x] = 1
 
        for k, v in d.items():
            if v == 1: return k
        return False

方法三:Counter

from collections import Counter

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        count = Counter(nums)
        for i in count:
            if count[i] == 1:
                return i

剑指 Offer 56 - I. 数组中数字出现的次数

方法一: 分组异或

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        ab = 0
        for x in nums:
            ab ^= x
        flag = -ab & ab # # 用于分组,找到一个 x,y 的不同位,也就是 异或的 1
        
        a = b = 0
        for x in nums:
            if x & flag == 0:
                a ^= x
            # else:
            #     b ^= x
        b = ab ^ a
        return [a, b]
class Solution {
    public int[] singleNumbers(int[] nums) {
        int ab = 0, a = 0, b = 0;
        for(int x: nums) ab ^= x;
        // ~ + < >> & ^ |
        int tmp = ab & ~ab + 1; // 取反加一,将 a, b 分组。
        for(int x: nums){
            if((x & tmp) > 0) a ^= x; // 说明与 a 一组
        }
        b = a ^ ab;
        return new int[]{a, b};
    }
}

方法二:

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:        
        d = {}
        for x in nums:
            if x in d: d[x] += 1
            else: d[x] = 1
            
        return [x for x in d if d[x]==1]

剑指 Offer 56 - II. 数组中数字出现的次数 II

方法一:

题解

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

方法二:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        ans = 0
        for i in range(32):
            cnt = 0 # 记录当前 bit 有多少个 1
            bit = 1 << i  # 记录当前要操作的 bit
            for x in nums:
                if x & bit: cnt += 1
            if cnt % 3: ans |= bit # 不等于 0 说明唯一出现的数字在这个 bit 上是 1
        return ans
class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for(int i = 0; i < 32; i++){
            int cnt = 0, bit = 1 << i;
            for(int x : nums){          
                if((x & bit) > 0) cnt++; // 与 python 不同 & 比 > 优先级低
            }
            if(cnt % 3 == 1) ans |= bit;
        }
        return ans;
    }
}

6201. 找出前缀异或的原始数组

class Solution:
    def findArray(self, pref: List[int]) -> List[int]:
        return [(pref[i-1] if i else 0) ^ x for i, x in enumerate(pref)]
        # 异或的性质:A ^ B = C 则 A ^ C = B, ans[i] = pref[i] ^ pref[i - 1]
        ans = [pref[0]]        
        for i in range(1, len(pref)):
            ans.append(pref[i - 1] ^ pref[i])
        return ans

为什么 Python 最后需要对返回值进行判断?

如果不这么做的话测试用例是 [-2,-2,1,1,-3,1,-3,-3,-4,-2] 的时候,就会输出 4294967292。 其原因在于Python是动态类型语言,在这种情况下其会将符号位置的 1 看成了值,而不是当作符号“负数”。 这是不对的。 正确答案应该是 - 4,-4 的二进制码是 1111…100,就变成 2^32-4 = 4294967292,解决办法就是 减去 2 ** 32 。

Python 位运算_补码_02

最左侧 1

英文名 count leading zeros 或 most significant bits problem。
给定一个整数,最左侧的 1 出现在什么位置,等价于寻找整数的最高有效位或者前置零的个数。
比如,0x128只有一个1,从右侧遍历 1 出现在第 7 位(从 0 开始计数)。

以下解法返回的均是从右侧遍历的位置。

移位操作,右移原数直到 0 为止。

def leftmostone(n):
    pos = -1
    while n:
        n >>= 1
        pos += 1
    return pos