leetcode--位运算

1 原码
2 反码
反码主要是针对负数的处理。在原码的基础上,符号位不变,其他数值位取反,即把1变成0,把0变成1。反码是为了在计算机中存储二进制,但非真正的二进制值,所以也不直接参与计算。
3 补码
补码是真正的二进制值了,主要也是针对负数。非负数不变,而负数是在反码的基础上加1。

在这里插入图片描述

功能 | 示例 | 位运算
———————-+—————————+——————–
去掉最后一位 | (101101->10110) | x >> 1
在最后加一个0 | (101101->1011010) | x << 1
在最后加一个1 | (101101->1011011) | x << 1+1
把最后一位变成1 | (101100->101101) | x or 1
把最后一位变成0 | (101101->101100) | x or 1-1
最后一位取反 | (101101->101100) | x xor 1
把右数第k位变成1 | (101001->101101,k=3) | x or (1 << (k-1))
把右数第k位变成0 | (101101->101001,k=3) | x and not (1 << (k-1))
右数第k位取反 | (101001->101101,k=3) | x xor (1 << (k-1))
取末三位 | (1101101->101) | x and 7
取末k位 | (1101101->1101,k=5) | x and (1 << k-1)
取右数第k位 | (1101101->1,k=4) | x >> (k-1) and 1
把末k位变成1 | (101001->101111,k=4) | x or (1 << k-1)
末k位取反 | (101001->100110,k=4) | x xor (1 << k-1)
把右边连续的1变成0 | (100101111->100100000) | x and (x+1)
把右起第一个0变成1 | (100101111->100111111) | x or (x+1)
把右边连续的0变成1 | (11011000->11011111) | x or (x-1)
取右边连续的1 | (100101111->1111) | (x xor (x+1)) >> 1
去掉右起第一个1的左边 | (100101000->1000) | x and (x xor (x-1))
取第i位的数字: (num >> i) & 1

取所有位上的数字:不断右移
lowbit(x)是x的二进制表达式中最低位的1所对应的值:x&(-x);

异或运算的性质:
x异或0=x
x异或x=0
a异或b=b异或a

二进制运算

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

res = 0
for i in range(32):
     res = res << 1
     res |= n&1
     n = n >> 1
 return res

191. 位1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
Brian Kernighan

def fun1(num):
	count = 0;
	while num > 0:
		num -= num & (~num + 1);
		count += 1
	return count
	
def fun2(num):
	count = 0;
	while num > 0:
		num = num & (num - 1)
		count += 1
	return count

231 Power of Two
326 Power of Three
342 Power of Four
这三道题目都是一个意思,就是判断一个数是否为2/3/4的幂,这几道题里面有通用的方法,也有各自的方法,我会分别讨论讨论。

1.首先肯定是循环了,将n一直除以3,当n最后为1的时候说明这是3的次方数;假如不是3的倍数,即n%3不为0,则不是3的幂
2.也可以用递归实现,原理其实大同小异(以Power of Four为例):
3.利用数学的方法来实现的(以Power of Three为例)。首先要知道的是,假如一个数是3的幂,那log3n一定是整数,接着利用高中数学中的换底公式(logab = logcb / logca),我们取c=10,则有log10n / log103一定是一个整数。那么我们只需判断log10n / log103是否为整数即可。是,n就是一个3的幂,反之亦然。
4.给定一个数m,怎么判断它是否整数呢?在C++中,只要m - (int)m == 0,即可说明它是整数。

判断传入的n的最右一位(最低位)是否为1,然后向右移位,统计1的个数,最后加入1的个数为1,那么肯定是2的幂了
将这个数-1,然后两个数按位相与,假如这个数是2的幂,所得的数肯定是0。
return n > 0 && (n & (n - 1)) == 0;
return n > 0 && (n & -n) == n;
假如一个数是4的幂指数,那么它首先肯定是2的幂。

与Power of Two相比,这些二进制数也是只有一个1,但同时也要注意到1的位置全是处于基数位。因此假如一个数是4的幂,它肯定在满足2的幂的条件下,又满足与0x55555555(10101010101…)按位相与等于它本身的条件

201. 数字范围按位与
给你两个整数 left 和 right ,表示区间 [left, right] ,返回此区间内所有数字 按位与 的结果(包含 left 、right 端点)。
我们的目的是求出两个给定数字的二进制字符串的公共前缀
「Brian Kernighan 算法」

shift = 0   
# 找到公共前缀
while m < n:
    m = m >> 1
    n = n >> 1
    shift += 1
return m << shift
while m < n:
    # 抹去最右边的 1
    n = n & (n - 1)
return n

338. 比特位计数
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
最高比特位,最低比特位

397. 整数替换
给定一个正整数 n ,你可以做如下操作:
如果 n 是偶数,则用 n / 2替换 n 。
如果 n 是奇数,则可以用 n + 1或n - 1替换 n 。
n 变为 1 所需的最小替换次数是多少?
对于偶数,直接左移即可
对于奇数:0bXX01直接-1,XX11直接+1。0b11(十进制的3)时,-1

405. 数字转换为十六进制数
给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。
注意:
十六进制中所有字母(a-f)都必须是小写。
十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符’0’来表示;对于其他情况,十六进制字符串中的第一个字符将不会是0字符。
给定的数确保在32位有符号整数范围内。
不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。

#如果是0直接返回"0"
if num == 0:
    return "0"
#十六进制字符串
s = "0123456789abcdef"
result = []
#因为是32位机器,所以是8个F
num = num & 0xFFFFFFFF
while num > 0:
    #从右至左,每4个bit位进行与操作,得到该十六进制的字符表示
    result.append(s[num & 0XF])
    #每次右移4位
    num = num >> 4
#结果取反序返回
return "".join(result[::-1])

401. 二进制手表
二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。
回溯

476. 数字的补数
给你一个 正整数 num ,输出它的补数。补数是对该数的二进制表示取反

利用二进制进行次数统计

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

137. 只出现一次的数字 II
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

res = 0
for i in range(32):
    total = sum([(num >> i) & 1 for num in nums])
    if total % 3:
        if i == 31:###处理负数
            res -= 1 << i
        else:
            res |= 1 << i
return res

260. 只出现一次的数字 III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
对所有数字异或,结果为a异或b,选择结果位为1的位对进行分组,保证ab会被分到两组,且相同数字会被分到一组。分组异或即可

res = 0
for num in nums:
    res = res ^ num

div = 1
while div & res == 0:
    div <<= 1
a = 0 
b = 0
for num in nums:
    if div & num:
        a ^= num
    else:
        b ^= num
return [a,b]

268. 丢失的数字
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
进阶:
你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?

169. 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
摩尔投票法

candidate = 0
vote = 0
for num in nums:
    if vote == 0:
        candidate = num
    if num == candidate:
        vote += 1
    else:
        vote -= 1
return candidate

187. 重复的DNA序列
所有 DNA 都由一系列缩写为 ‘A’,‘C’,‘G’ 和 ‘T’ 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来找出所有目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。

线性时间窗口切片 + HashSet

位操作:使用掩码实现常数时间窗口切片
A−>0=00 ,C−>1=01 ,G−>2=10 ,T−>3=11

Rabin-Karp:使用旋转哈希实现常数时间窗口切片

L, n = 10, len(s)
to_int = {'A': 0, 'C': 1, 'G': 2, 'T': 3}
nums = [to_int.get(s[i]) for i in range(n)]

bitmask = 0
seen, output = set(), set()
for start in range(n - L + 1):
    if start != 0:
        bitmask <<= 2
        # add a new 2-bits number in the last two bits
        bitmask |= nums[start + L - 1]
        bitmask &= ~(3 << 2 * L)
    else:
        for i in range(L):
            bitmask <<= 2
            bitmask |= nums[i]
    if bitmask in seen:
        output.add(s[start:start + L])
    seen.add(bitmask)
return output

318. 最大单词长度乘积
给定一个字符串数组 words,找到 length(word[i]) * length(word[j]) 的最大值,并且这两个单词不含有公共字母。你可以认为每个单词只包含小写字母。如果不存在这样的两个单词,返回 0。
26个字母先做成二进制表示的26位数,再位运算

n = len(words)
masks = [0] * n
lens = [0] * n
bit_number = lambda ch : ord(ch) - ord('a')

for i in range(n):
    bitmask = 0
    for ch in words[i]:
        # add bit number bit_number in bitmask
        bitmask |= 1 << bit_number(ch)
    masks[i] = bitmask
    lens[i] = len(words[i])
    
max_val = 0
for i in range(n):
    for j in range(i + 1, n):
        if masks[i] & masks[j] == 0:
            max_val = max(max_val, lens[i] * lens[j])
return max_val

389. 找不同
给定两个字符串 s 和 t,它们只包含小写字母。
字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
请找出在 t 中被添加的字母。

ret = 0
for ch in s:
   ret ^= ord(ch)

for ch in t:
   ret ^= ord(ch)
return chr(ret)

371. 两整数之和
不使用运算符 + 和 - ​​​​​​​,计算两整数 ​​​​​​​a 、b ​​​​​​​之和。

a + b 的问题拆分为 (a 和 b 的无进位结果) + (a 和 b 的进位结果)
无进位加法使用异或运算计算得出
进位结果使用与运算和移位运算计算得出
循环此过程,直到进位为 0

# 2^32
MASK = 0x100000000
# 整型最大值
MAX_INT = 0x7FFFFFFF
MIN_INT = MAX_INT + 1
while b != 0:
    # 计算进位
    carry = (a & b) << 1 
    # 取余范围限制在 [0, 2^32-1] 范围内
    a = (a ^ b) % MASK
    b = carry % MASK
return a if a <= MAX_INT else ~((a % MIN_INT) ^ MAX_INT)  

393. UTF-8 编码验证
UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:
对于 1 字节的字符,字节的第一位设为 0 ,后面 7 位为这个符号的 unicode 码。
对于 n 字节的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为 0 ,后面字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。

477. 汉明距离总和
两个整数的 汉明距离 指的是这两个数字的二进制数对应位不同的数量。
给你一个整数数组 nums,请你计算并返回 nums 中任意两个数之间汉明距离的总和。
将n!次运算压缩至n2次运算
第 i 位共有 c个 1,n−c 个 0,第 i 位上的汉明距离之和为c⋅(n−c)

n = len(nums)
ans = 0
for i in range(30):
    c = sum(((val >> i) & 1) for val in nums)
    ans += c * (n - c)
return ans

693. 交替位二进制数
给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。

762. 二进制表示中质数个计算置位
给定两个整数 L 和 R ,找到闭区间 [L, R] 范围内,计算置位位数为质数的整数个数。
(注意,计算置位代表二进制表示中1的个数。例如 21 的二进制表示 10101 有 3 个计算置位。还有,1 不是质数。)

primes = {2, 3, 5, 7, 11, 13, 17, 19}
return sum(bin(x).count('1') in primes for x in xrange(left, right+1))

784. 字母大小写全排列
给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

二进制从0到最大代表当前位置是否需要被替换
每一个字母都对应一个bit(1:upper, 0:lower), 数字除外
预先把所有的变化算出来

1178. 猜字谜
外国友人仿照中国字谜设计了一个英文版猜字谜小游戏,请你来猜猜看吧。
字谜的迷面 puzzle 按字符串形式给出,如果一个单词 word 符合下面两个条件,那么它就可以算作谜底:
单词 word 中包含谜面 puzzle 的第一个字母。
单词 word 中的每一个字母都可以在谜面 puzzle 中找到。
例如,如果字谜的谜面是 “abcdefg”,那么可以作为谜底的单词有 “faced”, “cabbage”, 和 “baggage”;而 “beefed”(不含字母 “a”)以及 “based”(其中的 “s” 没有出现在谜面中)都不能作为谜底。
返回一个答案数组 answer,数组中的每个元素 answer[i] 是在给出的单词列表 words 中可以作为字谜迷面 puzzles[i] 所对应的谜底的单词数目。

使用26位二进制代表word,puzzle

1255. 得分最高的单词集合
你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score。
请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。
单词拼写游戏的规则概述如下:
玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。
可以只使用字母表 letters 中的部分字母,但是每个字母最多被使用一次。
单词表 words 中每个单词只能计分(使用)一次。
根据字母得分情况表score,字母 ‘a’, ‘b’, ‘c’, … , ‘z’ 对应的得分分别为 score[0], score[1], …, score[25]。
本场游戏的「得分」是指:玩家所拼写出的单词集合里包含的所有字母的得分之和。

def maxScoreWords(self, words: List[str], letters: List[str], score: List[int]) -> int:
        n = len(words)
        chr_freq = [0 for _ in range(26)]   
        #letters中每个字母出现的pinlv
        for c in letters:
            chr_freq[ord(c) - ord('a')] += 1
        
        res = 0
        for state in range(1 << n):         
            need_chr_freq = [0 for _ in range(26)]      
            #需要的每个子母的个数
            #选中的单词,wi置为1
            for wi in range(n):
                if (state >> wi) & 1:
                    for c in words[wi]:
                        need_chr_freq[ord(c) - ord('a')] += 1
            flag = True                     #拥有的单词,能够满足当前的选择
            for ci in range(26):
                if need_chr_freq[ci] > chr_freq[ci]:
                    flag = False            #拥有的单词,满足不了state
                    break
            if flag == True:
                cur_score = 0               #当前state的得分
                for ci in range(26):
                    cur_score += need_chr_freq[ci] * score[ci]
                res = max(res, cur_score)   #更新res
        return res

1239. 串联字符串的最大长度
给定一个字符串数组 arr,字符串 s 是将 arr 某一子序列字符串连接所得的字符串,如果 s 中的每一个字符都只出现过一次,那么它就是一个可行解。
请返回所有可行解 s 中最长长度。

按照位查找

421. 数组中两个数的最大异或值
给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0 ≤ i ≤ j < n 。
进阶:你可以在 O(n) 的时间解决这个问题吗?

使用前缀树,哈希表等

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值