剑指 Offer II 001. 整数除法
题目描述
给定两个整数 a
和 b
,求它们的除法的商 a/b
,要求不得使用乘号 '*'
、除号 '/'
以及求余符号 '%'
。
- 整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231−1]。本题中,如果除法结果溢出,则返回 231 − 1
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xoh6Oh
题解
由于乘法不允许使用,不是2的偶次幂又不是很“快”,把右边算法想成对左边结果的分解,是不是就了然?
- 初始化返回值ret = 0
- 如果被除数大于除数,则除数扩大一倍
- 若被除数仍大于除数,这除数再次扩大一倍
- 直到除数下一次翻倍比被除数大时,将被除数减去除数,并将ret+=除数扩大的倍数,结束这一轮循环
- 重复2、3、4,直到被除数小于除数,终止循环并返回ret即可。
def divide(self, a: int, b: int) -> int: flag = False if (a>0 and b > 0) or (a < 0 and b < 0) else True a,b = abs(a),abs(b) def caculateCore(x,y): # where y is divider n = 1 while x > (y<<1): y<<=1 n<<=1 return n,y ret = 0 while a>=b: # cnt 是放大倍数, val 是除数b被方法cnt倍的值 cnt,val = caculateCore(a,b) ret += cnt a-=val ret = -ret if flag else ret return 2**31 -1 if ret >= 2**31 else ret
剑指 Offer II 002. 二进制加法
题目描述
给定两个 01 字符串 a
和 b
,请计算它们的和,并以二进制字符串的形式输出。
输入为 非空 字符串且只包含数字 1
和 0
。
题源:剑指 Offer II 002. 二进制加法 - 力扣(LeetCode) (leetcode-cn.com)
题解
牢记以下三点即可:
- 二进制的法则:逢二进一
- 进位的这个一,需要找个变量才存储它,才能在下一次的循环中获取
- 对于不等长的字符串,需要进行适当的优化与判断
def addBinary(self, a: str, b: str) -> str: #return bin(int(a,2)+int(b,2))[2:] ret = '' carryBit = 0 #用于存进位 i, j = len(a) - 1, len(b) - 1 while i >= 0 or j >= 0 or carryBit: if i >= 0: carryBit += ord(a[i]) - ord('0') if j >= 0: carryBit += ord(b[j]) - ord('0') ret += str(carryBit % 2) carryBit //= 2 i, j = i - 1, j - 1 return ret[::-1]
因为是从低位开始,放在ret的左边,所以需要ret[::-1]字符串转置一下
ord()函数
ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值,如果所给的 Unicode 字符超出了你的 Python 定义范围,则会引发一个 TypeError 的异常。
ord(c) # c是一个字符,返回值是对应十进制整数
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
题目描述
给定一个非负整数 n
,请计算 0
到 n
之间的每个数字的二进制表示中 1 的个数,并输出一个数组。
题源:剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 - 力扣(LeetCode) (leetcode-cn.com)
题解
使用python内置函数计算汉明重量
#调用库函数 对应c++中的 ((bitset<32>)n).count() bin(n).count('1')
使用自定义函数(位运算)计算汉明重量O(M) O(1)
def hammingWeight(n:int) -> int: # *巧用 n & (n-1) 把复杂度将为 O(M) O(1) P.S. n & (n-1) 还可以用来判断n是不是2的幂 res = 0 while(n>0): res+=1 n = n & (n-1) return res
对于这题来说这个方法不太好,没有动态规划快,因为不是计算单个数的汉明重量而是计算 [n] 的汉明重量
使用动态规划,汉明重量的特征
- 如果 i 是偶数,那么他刚好是 i/2 左移,他的汉明重量和 i/2 是一样的
- 如果 i 是奇数,那么他的汉明重量是 i-1 的汉明重量+1
def countBits(self, n: int) -> List[int]: #线性时间 O(n) 内用一趟扫描做到 #算法的空间复杂度为 O(n) #不使用任何内置函数(如 C++ 中的 __builtin_popcount )来执行此操作 使用动态规划 dp = [] dp.append(0) for i in range(1,n+1): dp.append(dp[i-1]+1) if i%2==1 else dp.append(dp[int(i/2)]) return dp
剑指 Offer II 004. 只出现一次的数字
题目描述
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
题源:剑指 Offer II 004. 只出现一次的数字 - 力扣(LeetCode) (leetcode-cn.com)
题解
FSM+位运算
参考K神题解:剑指 Offer II 004. 只出现一次的数字(有限状态自动机 + 位运算,清晰图解) - 只出现一次的数字 - 力扣(LeetCode) (leetcode-cn.com)
上面是对每个数的单独一个bit考虑的,应用到所有比特也成立,因为各bit之间互不干扰
而恰好这样的状态设置,就完成了 mod 3 的操作,所以最后高位 b 一定是全0, a 就是那个只出现一次的数
def singleNumber(self, nums: List[int]) -> int: # 对于最低位而言 这个位上有多少个1记为wt(b_0) wt(b_0) mod 3 \in {0,1,2} 三种状态 00 01 10 分别对应表示0,1,2 # 直接应用在所有的位上逻辑同样成立 a,b = 0,0 for num in nums: a = ~b &(num^a) # 状态高位是在状态地位修改后的基础上修改的 b = ~a &(num^b) # 因为状态的缘故 只关心状态啊=的地位就可以 就是说当所有的数加上之后,状态的高位肯定是0 return a
hash表
- 创建一个哈希表
- 循环数组,将每个元素挨个加入哈希表中
- 遍历哈希表中的数据,查找哪个数字只出现了一次返回。
def singleNumber(self, nums: List[int]) -> int: mydict = {} for num in nums: if num in mydict: mydict[num] += 1 else: mydict[num] = 1 for key, value in mydict.items(): if 1 == value:return key
剑指 Offer II 005. 单词长度的最大乘积
题目描述
给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。
- 单个字符串有重复没关系,主要是两个字符串不能出现任何一个相同的字符
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/aseY1I
题解
暴力搜+集合
def maxProduct(self, words: List[str]) -> int: # 暴力搜O(n^2) def twoStrProduct(str1,str2)->int: set1,set2 = set(str1),set(str2) set3 = set1.union(str2) #if len(set1)+len(set2)==len(str1)+len(str2) and len(set3)== len(set1)+len(set2): if len(set3)== len(set1)+len(set2): return len(str1)*len(str2) else: return 0 ret = 0 for i in range(len(words)-1): for j in range(i+1,len(words)): tmp = twoStrProduct(words[i],words[j]) if tmp>ret: ret = tmp return ret
二进制特性
- 利用二进制特性,将字符是否出现作为标志位,通过按位与判断是否有重复字符,比较操作的复杂度低
- 第一个循环为统计26个字母是否出现在word中,并存在一个数中。例如,如果 'a' 在 word[0] 中,那么 res[0] 的 LSB 便为1。
def maxProduct(self, words) -> int: '''二进制思路 26个字母对应26位长度的二进制数字,1表示含对应字母 位运算的一些trick可以快速计算是否重合 ''' res = [0]*len(words) max_res = 0 for i in range(len(words)): for c in words[i]: res[i] |= 1 << ord(c) - ord('a') for i in range(len(res)): for j in range(i, len(res)): if res[i] & res[j] == 0: # 按位与判断是否有重合,若结果为0,说明没有重合 max_res = max(max_res, len(words[i])*len(words[j])) return max_res 作者:frovvn 链接:https://leetcode-cn.com/problems/aseY1I/solution/li-yong-er-jin-zhi-te-xing-huo-counterji-zhpg/
剑指 Offer II 006. 排序数组中两个数字之和
题目描述
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。
假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kLl5u1
题解
对撞(双)指针
def twoSum(self, numbers: List[int], target: int) -> List[int]: #假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。 #升序排列 #双指针 i,j = 0,len(numbers)-1 while i<j: sum = numbers[i]+numbers[j] if sum<target: i+=1 elif sum > target: j-=1 else: return [i,j]
hash表
- 遍历 enumerate(numbers) ,即遍历 numbers
- 如果 num不在字典 d 中,字典新增键值对:键是该元素的目标匹配元素,值是该元素的下标
- 如果 遇到新的元素num 在字典中,意味着这个元素和之前某个元素匹配,返回 [之前元素的下标,当前元素的下标]
def twoSum(self, numbers: List[int], target: int) -> List[int]:
#假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。
#升序排列
#hash表
d = {}
for i, num in enumerate(numbers):
if num not in d:
d[target - num] = i
else:
return [d[num], i]
enumerate(list)用法
tetslist = [0,0,3,4] valuelist = list(range(len(tetslist))) mydict = enumerate(tetslist) for i in mydict: print(i) output: (0, 0) (1, 0) (2, 3) (3, 4)