回溯
46. 全排列:不含重复元素
def backtrack():
if len(path) == len(nums):
result.append(list(path))
else:
for i in range(len(nums)):
# 剪枝
if nums[i] not in path:
path.append(nums[i])
backtrack()
path.pop()
47. 全排列 II:包含重复元素
剪枝条件为
如果该元素被选择过 或者 该元素不是第一个元素且在 nums 列表中的上一个元素和该元素相同(要求 nums 是排好序的),则跳过
def backtrack():
if len(path) == len(nums):
result.append(list(path))
else:
for i in range(len(nums)):
# 理解剪枝
if selected[i] or (i > 0 and nums[i] == nums[i - 1] and not selected[i - 1]):
continue
path.append(nums[i])
selected[i] = True
backtrack()
path.pop()
selected[i] = False
直接使用回溯 + 剪枝
# 剪枝
if (path and path[-1] > i) or selected[i]:
continue
if (sum(path) + candidates[i] > target)
or (path and candidates[i] < path[-1]):
continue
if visited[i] # 只能出现一次
or (sum(path) + candidates[i] > target) # 超出
or (path and candidates[i] < path[-1]) # 排除比当前小的
# 因为candidates包含重复,结果会有重复的组合,从这一步通过剪枝去掉
or (i > 0 and candidates[i] == candidates[i - 1] and not visited[i - 1]):
continue
动态规划
动态方程:
# dp[i][j]表示下标i到j的子串能否构成回文串
if dp[i+1][j-1] and s[i] == s[j]:
dp[i][j] = True
初始化:
for i in range(n):
dp[i][i] = True
# 维护长度为2的判断,当j - i = 1时,仅需判断s[i]与s[j]是否相同
# 因此需要dp[i + 1][j - 1]条件恒为true
if i + 1 < n:
dp[i + 1][i] = True
一维角度
# 初始条件
dp[0] = dp[1] = 1
# 状态转移方程
dp[i] = dp[i-1] + dp[i-2]
进阶:二维角度,把爬楼梯的steps看作list,每一次都在list中进行选择步数
例如 steps = [1, 2, 3],设计算法实现爬n层楼梯的可能性
# 先确定爬楼梯阶数,再选择一次爬楼的step步长
dp[0] = 1
for i in range(2, n + 1):
for j in steps:
if i >= j:
dp[i] += dp[i - j]
# 先确定一次爬楼的step步长,再选择爬楼梯阶数
dp[0] = 1
for s in steps:
for i in range(1, n + 1):
if i - s > 0:
dp[i] += dp[i - s]
先枚举coin再枚举target,避免重复情况计算
for coin in coins:
for i in range(coin, amount + 1, 1):
dp[i] += dp[i - coin]
二叉树
def depth(r):
if r is None:
return 0
# 递归求左,右子树的深度
R = depth(r.left)
L = depth(r.right)
# 以r为根节点,经过节点数量的最大值为L+R+1
# 注意题目要求直径即节点数-1,最终求ans[0]-1
# 保持ans始终为最大值
ans[0] = max(ans[0], L + R + 1)
# 传递递归函数的返回值,即为以r为根节点的最大【深度】,而不是【直径】
return max(L, R) + 1
124. 二叉树中的最大路径和
可以当作是上题的变形,即depth函数换成maxGain,求以该节点为根的最大收益
def maxGain(r):
if r is None:
return 0
# 考虑负数,和0做比较
L = max(maxGain(r.left), 0)
R = max(maxGain(r.right), 0)
ans[0] = max(ans[0], L + R + r.val)
# 函数功能为求该节点为根节点时的最大收益
# 只用求某一分支的最大收益加上该节点的值,注意不要和【路径和】搞混
return max(L, R) + r.val
236. 二叉树的最近公共祖先
若 root 是 p, q 的 最近公共祖先 ,则只可能为以下情况之一:
1.p 和 q 在 root 的子树中,且分列 root 的 异侧(即分别在左、右子树中);
2.p=root ,且 q 在 root 的左或右子树中;
3.q=root ,且 p 在 root 的左或右子树中;
# 如果根节点为空,说明没找到,返回空值
# 或者递归遇到了p或者q,直接返回p或者q
if root is None or root == p or root == q:
return root
# 递归查询左右子树p和q的最近公共祖先
lson = self.lowestCommonAncestor(root.left, p, q)
rson = self.lowestCommonAncestor(root.right, p, q)
# 如果left 和 right都不为空,说明在root的左右子树分别找到p和q,此时root就是最近公共节点;
# 情况 1
if lson is not None and rson is not None:
return root
# 如果left和right其中一个为空,说明只在一边找到p或q,那么先找到的p或q就是最近公共节点
# 情况2或3
if lson is None:
return rson
return lson
分治
n = len(nums)
if n < 2:
return nums[n - k]
def partition(arr, low, high):
i = (low - 1) # 最小元素索引
pivot = arr[high]
for j in range(low, high):
# 当前元素小于或等于 pivot
if arr[j] <= pivot:
i = i + 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
def quickSort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
if pi == n - k:
return nums[pi]
if pi > n - k:
return quickSort(arr, low, pi - 1)
if pi < n - k:
return quickSort(arr, pi + 1, high)
return quickSort(nums, 0, len(nums) - 1)