学习使用工具
剑指Offer http://itmyhome.com/sword-means-offer/sword-means-offer.pdf
LeetCode的剑指Offer题库 https://leetcode.cn/problemset/all/
剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true
。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false
。
限制:
0 <= 树的结点个数 <= 10000
解法:
先写一个用于计算当前节点深度的函数辅助递归。主函数递归,如果当前节点为空,返回True;如果当前节点深度满足平衡,继续对左右子树进行递归判定;如果不满足,返回False。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
deep = 1
deep += max(self.maxDepth(root.left), self.maxDepth(root.right))
return deep
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
if abs(self.maxDepth(root.left) - self.maxDepth(root.right)) < 2:
return self.isBalanced(root.left) and self.isBalanced(root.right)
else:
return False
剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums
里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums.length <= 10000
解法:
想了半天怎么把时间复杂度压进O(n),然后又想了半天怎么把空间复杂度压进O(1),一看答案竟然是用异或……
考虑异或操作的性质:对于两个操作数的每一位,相同结果为 0,不同结果为 1。那么在计算过程中,成对出现的数字的所有位会两两抵消为 0,最终得到的结果就是那个出现了一次的数字。
那么这一方法如何扩展到找出两个出现一次的数字呢?如果我们可以把所有数字分成两组,使得:
- 两个只出现一次的数字在不同的组中;
- 相同的数字会被分到相同的组中。
那么对两个组分别进行异或操作,即可得到答案的两个数字。这是解决这个问题的关键。
分组方法:
记这两个只出现了一次的数字为a和b,那么所有数字异或的结果就等于a和b异或的结果,记为x。将x写成二进制形式,其中。我们任选一个不为0的按照第i位给原来的序列分组,如果该位为0就分到第一组,否则就分到第二组。
def singleNumbers(self, nums: List[int]) -> List[int]:
ret = functools.reduce(lambda x, y: x ^ y, nums)
div = 1
while div & ret == 0:
div <<= 1
a, b = 0, 0
for n in nums:
if n & div:
a ^= n
else:
b ^= n
return [a, b]
剑指 Offer 56 - II. 数组中数字出现的次数 II
在一个数组 nums
中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 10000
1 <= nums[i] < 2^31
解法:
不考虑空间复杂度当然是乱做,但是考虑时间复杂度O(n)和空间复杂度O(1)的话依然要使用位运算。考虑数字的二进制形式,对于出现三次的数字,各 二进制位 出现的次数都是 333 的倍数。 因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字。
使用与运算 ,可获取二进制数字 num 的最右一位,配合无符号右移操作 ,可获取 num所有位置的值。建立一个长度为 32 的数组 counts,通过以上方法可记录所有数字的各二进制位的1的出现次数。将 counts各元素对 3 求余,则结果为 “只出现一次的数字” 的各二进制位。利用 左移操作和或运算,可将 counts 数组中各二进位的值恢复,复原后返回即可。
def singleNumber(self, nums: List[int]) -> int:
counts = [0] * 32
for num in nums:
for j in range(32):
counts[j] += num & 1
num >>= 1
res, m = 0, 3
for i in range(32):
res <<= 1
res |= counts[31 - i] % m
return res if counts[31] % m == 0 else ~(res ^ 0xffffffff)
剑指 Offer 57. 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
解法:
梦开始的地方。双指针各自从头尾开始遍历即可。
def twoSum(self, nums: List[int], target: int) -> List[int]:
i, j = 0, len(nums) - 1
ans = nums[i] + nums[j]
while not ans == target:
if ans > target:
j -= 1
else:
i += 1
ans = nums[i] + nums[j]
return [nums[i], nums[j]]