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 与 &
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 。
最左侧 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