LC回溯+动态规划+分治+二叉树hot题

回溯

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

77. 组合

直接使用回溯 + 剪枝

# 剪枝
if (path and path[-1] > i) or selected[i]:
       continue

39. 组合总和

if (sum(path) + candidates[i] > target) 
    or (path and candidates[i] < path[-1]):
       continue

40. 组合总和 II

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

动态规划

5. 最长回文子串

动态方程:

# 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

70. 爬楼梯

一维角度

# 初始条件
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]

518. 零钱兑换 II

先枚举coin再枚举target,避免重复情况计算

for coin in coins:
   for i in range(coin, amount + 1, 1):
      dp[i] += dp[i - coin]

二叉树

543. 二叉树的直径

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

分治

215. 数组中的第K个最大元素

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值