目录
三、剑指 Offer 17. 打印从 1 到最大的 n 位数
一、剑指 Offer 07. 重建二叉树 ☆
1.1 题求
1.2 求解
官方说明
# 36ms - 98.80%
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 前序遍历性质: 节点按 [ 根节点 | 左子树 | 右子树 ] 排序
# 中序遍历性质: 节点按 [ 左子树 | 根节点 | 右子树 ] 排序
# root: 前序遍历节点索引, left/right: 中序遍历节点索引
def recur(root, left, right):
if left > right: # 索引越界, 递归终止
return
# 根据根节点在前序遍历位置索引 root 建立当前根节点 node
node = TreeNode(preorder[root])
# i 为当前根节点 node 在中序遍历中的位置索引, 对中序遍历划分根节点、左子树、右子树
i = dic[preorder[root]]
# 左子树递归 (left ~ i-1 为当前根节点 node 在中序遍历的左子树索引范围)
node.left = recur(root+1, left, i-1)
# root+1:当前根节点在前序遍历的位置索引 root + 1
# = 左子树根节点在前序遍历的位置索引
# 右子树递归 (i+1 ~ right 为当前根节点 node 在中序遍历的右子树索引范围)
node.right = recur(i-left + root+1, i+1, right)
# i-left+root+1:左子树长度 (i-left) + 当前根节点在前序遍历的位置索引 root + 1
# = 右子树根节点在前序遍历的位置索引
# 回溯返回当前根节点 node
return node
# 记录中序遍历的节点值与索引的映射
dic = {}
for i in range(len(inorder)):
dic[inorder[i]] = i
return recur(0, 0, len(inorder)-1)
1.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/99lxci/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/99ljye/
二、剑指 Offer 16. 数值的整数次方
2.1 题求
2.2 求解
法一:递归
# 24ms - 99.41%
class Solution:
def myPow(self, x: float, n: int) -> float:
# 分解规律
# 2^7 = (2^3)^2 * 2 = ((2^1)^2 * 2)^2 * 2
# 2^-7 = (2^-4)^2 * 2 = ((2^-2)^2)^2 * 2 = (((2^-1)^2)^2)^2 * 2
# 最小子问题
if n == 0 or n == 1 or n == -1:
return x ** n
# 商
a = n // 2 # 7 // 2 = 3, -7 // 2 = -4 注意区别
# 余数
b = n % 2 # 7 % 2 = -7 % 2 = 1
# 子问题求解
c = self.myPow(x, a)
# 当前问题结果
return c * c if b == 0 else c * c * x
法一改:递归
class Solution:
def myPow(self, x: float, n: int) -> float:
# 最小子问题
if -1 <= n <= 1:
return x ** n
# 商 - 用向右移位运算符代替除 2 操作
a = n >> 1 # n // 2
# 余数 - 用位运算代替求余操作
b = n & 1 # n % 2
# 子问题求解
c = self.myPow(x, a)
# 当前问题结果
return c * c if b == 0 else c * c * x
官方说明
# 28ms - 97.27%
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0.0:
return 0.0
res = 1
if n < 0:
x, n = 1 / x, -n
while n:
if n & 1:
res *= x
x *= x
n >>= 1
return res
2.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57rwmg/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57p2pv/
三、剑指 Offer 17. 打印从 1 到最大的 n 位数
3.1 题求
3.2 求解
法一:列表推导式
# 36ms - 92.36%
class Solution:
def printNumbers(self, n: int) -> List[int]:
return [i for i in range(1, 10**n)]
# 32ms - 97.31%
class Solution:
def printNumbers(self, n: int) -> List[int]:
return [i for i in range(1, int('1' + '0' * n))]
官方说明
class Solution:
def printNumbers(self, n: int) -> List[int]:
res = []
for i in range(1, 10 ** n):
res.append(i)
return res
class Solution:
def printNumbers(self, n: int) -> List[int]:
return list(range(1, 10 ** n))
class Solution:
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n: # 终止条件:已固定完所有位
res.append(''.join(num)) # 拼接 num 并添加至 res 尾部
return
for i in range(10): # 遍历 0 - 9
num[x] = str(i) # 固定第 x 位为 i
dfs(x + 1) # 开启固定第 x + 1 位
num = ['0'] * n # 起始数字定义为 n 个 0 组成的字符列表
res = [] # 数字字符串列表
dfs(0) # 开启全排列递归
return ','.join(res) # 拼接所有数字字符串,使用逗号隔开,并返回
class Solution:
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0': res.append(s)
if n - self.start == self.nine: self.start -= 1
return
for i in range(10):
if i == 9: self.nine += 1
num[x] = str(i)
dfs(x + 1)
self.nine -= 1
num, res = ['0'] * n, []
self.nine = 0
self.start = n - 1
dfs(0)
return ','.join(res)
class Solution:
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0': res.append(int(s))
if n - self.start == self.nine: self.start -= 1
return
for i in range(10):
if i == 9: self.nine += 1
num[x] = str(i)
dfs(x + 1)
self.nine -= 1
num, res = ['0'] * n, []
self.nine = 0
self.start = n - 1
dfs(0)
return res
3.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/594wfg/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5912jv/
四、剑指 Offer 33. 二叉搜索树的后序遍历序列 ☆
4.1 题求
4.2 求解
官方说明
# 24ms - 99.34%
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def recur(i, j):
# 递归至区间越界都未发现问题, 符合
if i >= j:
return True
# 区间左起点
p = i
while postorder[p] < postorder[j]: # 当前左子树范围
p += 1
# 找到第一个大于当前子树的根节点 postorder[j] 的节点索引 m
m = p
while postorder[p] > postorder[j]: # 当前右子树范围
p += 1
# 根节点索引 j, 左子树区间 [i, m-1], 右子树区间 [m, j-1]
return p == j and recur(i, m-1) and recur(m, j-1)
return recur(0, len(postorder)-1)
class Solution:
def verifyPostorder(self, postorder: [int]) -> bool:
stack, root = [], float("+inf")
for i in range(len(postorder) - 1, -1, -1):
if postorder[i] > root: return False
while(stack and postorder[i] < stack[-1]):
root = stack.pop()
stack.append(postorder[i])
return True
4.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vwxx5/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vwbf6/
五、剑指 Offer 51. 数组中的逆序对 ☆
5.1 题求
5.2 求解
法一:选择排序
# 超出时间限制
class Solution:
def reversePairs(self, nums: List[int]) -> int:
# 逆序对数 = 当前序列总对数 - 当前序列通过两两交换排序到全逆序的最少移动次数
# = len(nums) * [len(nums)-1] // 2 - swap_times
### 插入排序
n = len(nums)
swap_times = 0 # 全逆序交换/排列次数
all_kinds = n * (n-1) // 2 # 总组合数
for i in range(1, n):
# 当前待比较和交换的元素值及其索引
cur_val = nums[i]
cur_idx = i
while cur_idx > 0 and cur_val >= nums[cur_idx-1]: # 顺序对
nums[cur_idx-1], nums[cur_idx] = nums[cur_idx], nums[cur_idx-1] # 交换为逆序对
swap_times += 1 # 交换次数 +1
cur_idx -= 1 # cur_val 的 idx 更新 (从右往左往回比较)
print(nums)
return all_kinds - swap_times
官方说明
# 1188ms - 86.05
class Solution:
def reversePairs(self, nums: List[int]) -> int:
# 归并排序 (顺序-从小到大)
def merge_sort(l, r):
# 终止条件 左右边界越界
if l >= r:
return 0
# 递归划分
m = (l + r) // 2 # 数组中点 m
res = merge_sort(l, m) + merge_sort(m+1, r) # 左、右子数组逆序数之和
# 合并阶段
i, j = l, m+1 # i, j 分别指向 左/右子数组 的 首元素/起点
tmp[l: r+1] = nums[l: r+1] # 当前左/右子数组索引范围切片 拷贝 - 辅助数组
# 遍历左/右子数组索引范围切片 [l: r+1]
for k in range(l, r+1):
# 当 i = m+1 时, 代表左子数组已合并完, 故添加右子数组当前元素 tmp[j], 并令 j += 1
if i == m+1:
nums[k] = tmp[j]
j += 1
# 否则, 当 j = r+1 时, 代表右子数组已合并完,故添加左子数组当前元素 tmp[i], 并令 i += 1
# 否则, 当 tmp[i] ≤ tmp[j] (顺序) 时, 添加左子数组当前元素 tmp[i], 并执行 i += 1
elif j == r+1 or tmp[i] <= tmp[j]:
nums[k] = tmp[i] # 小的先入 (顺序排列)
i += 1
# 否则, 当 tmp[i] > tmp[j] (逆序) 时, 添加右子数组当前元素 tmp[j], 并执行 j += 1, 此时构成 m−i+1 个「逆序对, 统计添加至 res
else:
nums[k] = tmp[j] # 小的先入 (顺序排列)
j += 1
res += m-i+1 # 统计逆序对
# 返回当前逆序对数
return res
tmp = [0 for _ in range(len(nums))] # 辅助数组
return merge_sort(0, len(nums)-1)
5.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/o58jfs/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/o53yjd/