【leetcode】动态规划刷题总结-基础题

什么是动态规划

动态规划(Dynamic Programming,简称DP),如果某一问题有很多重叠子问题,使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。

动态规划的解题步骤

五步曲:

  • 确定dp数组(dp table)以及下标的含义
  • 确定递推公式(根据dp数组的定义,运用数学归纳法的思想,假设dp[0...i-1]都已知,想办法求出dp[i],一旦这一步完成,整个题目基本就解决了。但如果无法完成这一步,很可能就是dp数组的定义不够恰当,需要重新定义dp数组的含义;或者可能是dp数组存储的信息还不够,不足以推出下一步的答案,需要把dp数组扩大成二维数组甚至三维数组)
  • dp数组如何初始化
  • 确定遍历顺序
  • 举例推导、打印dp数组

爬楼梯问题

LeetCode746题 使用最小花费爬楼梯

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        # dp[i]表示跳到i的最小花费,只需记录最近两个状态
        # 由于跳到前两个位置不需要花费,所以初始化为0
        dp = [0, 0]
        for i in range(2, len(cost)):
            tmp = min(dp[0] + cost[i - 2], dp[1] + cost[i - 1])
            dp[0] = dp[1]
            dp[1] = tmp
        return min(dp[0] + cost[len(cost) - 2], dp[1] + cost[len(cost) - 1])

LeetCode91题 解码方法

class Solution:
    def numDecodings(self, s: str) -> int:
        if s[0] == '0':
            return 0
        # dp[i]代表s[:i+1]的解码方法数,只需记录最近两个状态
        dp = [1, 1]
        for i in range(1, len(s)):
            # 无法解码
            if s[i] == '0' and s[i - 1] not in {'1', '2'}:
                return 0
            # s[i]只能和s[i-1]一起解码
            elif s[i] == '0' and s[i - 1] in {'1', '2'}:
                r = dp[0]
            # s[i]只能独自解码
            elif s[i - 1] == '0' or int(s[i - 1:i + 1]) > 26:
                r = dp[1]
            # 其他有两种解码方式情况
            else:
                r = dp[0] + dp[1]
            dp[0], dp[1] = dp[1], r
        return dp[-1]

打家劫舍问题 

LeetCode198题 打家劫舍

class Solution:
    def rob(self, nums: List[int]) -> int:
        # 二维数组解法
        # dp = [[0] * 2 for _ in range(len(nums))]
        # 一维数组解法
        dp = [0] * 2
        for i in range(len(nums)):
            # dp[i][0] = max(dp[i - 1][0], dp[i - 1][1])
            # dp[i][1] = dp[i - 1][0] + nums[i]
            dp[0], dp[1] = max(dp[0], dp[1]), dp[0] + nums[i]
        return max(dp[0], dp[1])

 LeetCode3186题 施咒的最大总伤害

class Solution:
    def maximumTotalDamage(self, power: List[int]) -> int:
        # 重构power数组,使之转化为打家劫舍问题
        power.sort()
        power_ = []
        power_sum = []
        for i in range(len(power)):
            if i > 0 and power[i] == power[i - 1]:
                power_sum[-1] += power[i]
            else:
                power_.append(power[i])
                power_sum.append(power[i])
        # 打家劫舍问题求解
        dp_pre = [0, 0]
        dp = [0, 0]
        for i in range(len(power_)):
            dp_copy = dp[:]
            if i >= 2 and power_[i] - power_[i - 2] == 2:
                dp[0], dp[1] = max(dp[0], dp[1]), dp_pre[0] + power_sum[i]
            elif i >= 1 and power_[i] - power_[i - 1] <= 2:
                dp[0], dp[1] = max(dp[0], dp[1]), dp[0] + power_sum[i]
            else:
                dp[0], dp[1] = max(dp[0], dp[1]), max(dp[0], dp[1]) + power_sum[i]
            dp_pre = dp_copy
        return max(dp[0], dp[1])

LeetCode213题 打家劫舍II

将环形问题转化为线性问题,分别计算不考虑首尾元素的情况

class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1:
            return nums[0]
        dp_1 = [0] * 2
        # 不考虑尾元素
        for i in range(len(nums) - 1):
            dp_1[0], dp_1[1] = max(dp_1[0], dp_1[1]), dp_1[0] + nums[i]
        dp_2 = [0] * 2
        # 不考虑首元素
        for i in range(1, len(nums)):
            dp_2[0], dp_2[1] = max(dp_2[0], dp_2[1]), dp_2[0] + nums[i]
        return max(dp_1[0], dp_1[1], dp_2[0], dp_2[1])

LeetCode337题 打家劫舍III

树形DP解法,遍历二叉树的递归函数返回值为DP数组

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def rob(self, root: Optional[TreeNode]) -> int:
        res = self.traversal(root)
        return max(res[0], res[1])

    def traversal(self, root):
        # dp数组含义:
        # 索引为0记录不偷该节点所得到的的最大金钱
        # 索引为1记录偷该节点所得到的的最大金钱
        if root is None:
            return [0, 0]
        left = self.traversal(root.left)
        right = self.traversal(root.right)
        return [max(left[0], left[1]) + max(right[0], right[1]), left[0] + right[0] + root.val]

其他典型问题

LeetCode343题 整数拆分

class Solution:
    def integerBreak(self, n: int) -> int:
        # dp[i]代表拆i的最大乘积
        dp = [1] * (n + 1)
        for i in range(3, n + 1):
            # 拆分为2个元素最大乘积
            tmp = i // 2 * (i - i // 2)
            # 搜索拆分为大于2个元素的最大乘积
            for j in range(1, i - 1):
                tmp = max(tmp, j * dp[i - j])
            dp[i] = tmp
        return dp[n]

难在dp数组定义、递推公式的问题

LeetCode1696题 跳跃游戏VI

动态规划 + 单调队列解法:可以将dp数组设置为一个单调队列,只保留之前k个位置有机会成为最大的分数,这样队首元素就代表着前k个位置的最大分数。降低时间复杂度O(k*n)->O(n)

class Solution:
    def maxResult(self, nums: List[int], k: int) -> int:
        from collections import deque
        # 维护一个单调队列,降低时间复杂度O(k*n)->O(n)
        queue = deque([nums[0]])
        for i in range(1, len(nums)):
            # 此时nums[i]代表到达索引i的最大得分
            nums[i] = queue[0] + nums[i]
            # 到达第k步时,开始弹出符合条件的元素
            if i - k >= 0 and queue[0] == nums[i - k]:
                queue.popleft()
            # 将nums[i]加入单调队列
            while queue and queue[-1] < nums[i]:
                queue.pop()
            queue.append(nums[i])
        return nums[-1]

LeetCode221题 最大正方形

dp[i][j]定义为以(i, j)为右下角的最大正方形边长

递推公式:dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        # dp[i][j]代表以(i, j)为右下角的最大正方形边长
        # dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1
        res = 0
        m, n = len(matrix), len(matrix[0])
        dp = [[0] * n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == '0':
                    continue
                if i == 0 or j == 0:
                    dp[i][j] = 1
                else:
                    dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1
                res = max(res, dp[i][j])
        return res * res

LeetCode787题 K站中转内最便宜的航班

class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        # dp[t][i]代表从src出发,t次中转到i的最小花费
        # dp[t][i] = 对可到达i的j 取min{dp[t−1][j] + price(j,i)}
        # dp数组初始化
        dp = [float('inf')] * n
        dp[src] = 0
        res = dp[dst]
        for t in range(k + 1):
            tmp_dp = [float('inf')] * n
            for s, d, p in flights:
                tmp_dp[d] = min(tmp_dp[d], dp[s] + p)
            res = min(res, tmp_dp[dst])
            dp = tmp_dp
        if res == float('inf'):
            return -1
        return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值