内容来自刘宇波老师玩转算法面试
动态规划解题套路框架
状态转移 + 重叠子问题 + 最优子结构(通过求子问题的最优解,可以获得原问题的最优解)
base case -> 状态 -> 选择 -> 定义 dp 数组 / 函数的含义
1、状态 = 变量
2、base case = 最基础的变量
3、如何选择 = 求最值 = 最优子结构
| |
| def dp(状态1, 状态2, ...): |
| for 选择 in 所有可能的选择: |
| |
| result = 求最值(result, dp(状态1, 状态2, ...)) |
| return result |
| |
| |
| |
| dp[0][0][...] = base case |
| |
| for 状态1 in 状态1的所有取值: |
| for 状态2 in 状态2的所有取值: |
| for ... |
| dp[状态1][状态2][...] = 求最值(选择1,选择2...) |
1、斐波那契数
509 - 斐波那契数
1.1、Fibonacci Sequence
| |
| |
| |
| |
| public static int fib(int n) { |
| if (n <= 1) return n; |
| return fib(n - 1) + fib(n - 2); |
| } |
1.2、重叠子问题


1.3、自顶向下解决问题「记忆化搜索」
| |
| |
| |
| |
| public static int fib(int n) { |
| int[] memo = new int[n + 1]; |
| Arrays.fill(memo, -1); |
| return fib(n, memo); |
| } |
| |
| private static int fib(int n, int[] memo) { |
| if (n <= 1) return n; |
| |
| if (memo[n] != -1) return memo[n]; |
| |
| memo[n] = fib(n - 1, memo) + fib(n - 2, memo); |
| return memo[n]; |
| } |
1.4、自底向上解决问题「动态规划」
| |
| |
| |
| |
| public static int fib(int n) { |
| if (n <= 1) return n; |
| |
| int[] memo = new int[n + 1]; |
| memo[0] = 0; |
| memo[1] = 1; |
| |
| for (int i = 2; i <= n; i++) { |
| memo[i] = memo[i - 1] + memo[i - 2]; |
| } |
| |
| return memo[n]; |
| } |
1.5、降低空间复杂度
| |
| |
| |
| |
| public static int fib(int n) { |
| int[] memo = new int[2]; |
| memo[0] = 0; |
| memo[1] = 1; |
| |
| for (int i = 2; i <= n; i++) { |
| memo[i % 2] = memo[i % 2] + memo[(i - 1) % 2]; |
| } |
| |
| return memo[n % 2]; |
| } |
2、什么是「动态规划」
将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案

3、爬楼梯
70 - 爬楼梯

| public static int climbStairs1(int n) { |
| int[] memo = new int[n + 1]; |
| memo[0] = 1; |
| memo[1] = 1; |
| |
| for (int i = 2; i <= n; i++) { |
| memo[i] = memo[i - 1] + memo[i - 2]; |
| } |
| |
| return memo[n]; |
| } |
| |
| |
| |
| |
| public static int climbStairs2(int n) { |
| int[] memo = new int[2]; |
| memo[0] = 1; |
| memo[1] = 1; |
| |
| int res = 1; |
| for (int i = 2; i <= n; i++) { |
| res = memo[0] + memo[1]; |
| memo[0] = memo[1]; |
| memo[1] = res; |
| } |
| |
| return res; |
| } |
更多问题
120 - 三角形最小路径和
64 - 最小路径和
4、最优子结构
最优子结构:通过求子问题的最优解,可以获得原问题的最优解
更多问题
279 - 完全平方数
91 - 解码方法
62 - 不同路径
63 - 不同路径 II
4.1、整数拆分
343 - 整数拆分
4.1.1、图示


4.1.2、记忆化搜索
| public class Solution { |
| |
| private static int[] memo; |
| |
| public static int integerBreak(int n) { |
| memo = new int[n + 1]; |
| return breakInteger(n); |
| } |
| |
| |
| |
| |
| private static int breakInteger(int n) { |
| if (n == 2) return 1; |
| |
| if (memo[n] != 0) return memo[n]; |
| |
| int res = -1; |
| for (int i = 1; i <= n - 1; i++) { |
| |
| res = max3(res, i * (n - i), i * breakInteger(n - i)); |
| } |
| memo[n] = res; |
| |
| return res; |
| } |
| |
| private static int max3(int a, int b, int c) { |
| return Math.max(Math.max(a, b), c); |
| } |
| } |
4.1.3、动态规划
| public class Solution { |
| |
| public static int integerBreak(int n) { |
| |
| int[] memo = new int[n + 1]; |
| memo[2] = 1; |
| |
| |
| for (int i = 3; i <= n; i++) { |
| |
| int res = -1; |
| for (int j = 1; j <= i - 1; j++) { |
| |
| res = max3(res, j * (i - j), j * memo[i - j]); |
| } |
| memo[i] = res; |
| } |
| |
| return memo[n]; |
| } |
| |
| private static int max3(int a, int b, int c) { |
| return Math.max(Math.max(a, b), c); |
| } |
| } |
4.2、零钱兑换
322 - 零钱兑换
4.2.1、递归
| public static int coinChange(int[] coins, int amount) { |
| return dp(coins, amount); |
| } |
| |
| private static int dp(int[] coins, int amount) { |
| if (amount < 0) return -1; |
| if (amount == 0) return 0; |
| |
| int res = Integer.MAX_VALUE; |
| for (int coin : coins) { |
| int subProblem = dp(coins, amount - coin); |
| if (subProblem == -1) continue; |
| res = Math.min(res, 1 + subProblem); |
| } |
| |
| return res == Integer.MAX_VALUE ? -1 : res; |
| } |
4.2.2、记忆化搜索
| public static int coinChange(int[] coins, int amount) { |
| int[] memo = new int[amount + 1]; |
| Arrays.fill(memo, -10); |
| return dp(coins, amount, memo); |
| } |
| |
| private static int dp(int[] coins, int amount, int[] memo) { |
| if (amount < 0) return -1; |
| if (amount == 0) return 0; |
| |
| if (memo[amount] != -10) return memo[amount]; |
| |
| int res = Integer.MAX_VALUE; |
| for (int coin : coins) { |
| int subProblem = dp(coins, amount - coin, memo); |
| if (subProblem == -1) continue; |
| res = Math.min(res, 1 + subProblem); |
| } |
| memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; |
| |
| return memo[amount]; |
| } |
4.2.3、动态规划
| public static int coinChange(int[] coins, int amount) { |
| int[] memo = new int[amount + 1]; |
| Arrays.fill(memo, amount + 1); |
| |
| memo[0] = 0; |
| for (int i = 1; i <= amount; i++) { |
| |
| for (int coin : coins) { |
| if (i < coin) continue; |
| memo[i] = Math.min(memo[i], 1 + memo[i - coin]); |
| } |
| } |
| |
| return memo[amount] == amount + 1 ? -1 : memo[amount]; |
| } |
5、状态的定义和状态转移
198 - 打家劫舍
更多问题
213 - 打家劫舍 II
337 - 打家劫舍 III
309 - 最佳买卖股票时机含冷冻期
5.1、图示



5.2、记忆化搜索
| public static int rob(int[] nums) { |
| if (nums == null || nums.length == 0) return 0; |
| int[] memo = new int[nums.length]; |
| Arrays.fill(memo, -1); |
| return rob(nums, 0, memo); |
| } |
| |
| |
| |
| |
| private static int rob(int[] nums, int x, int[] memo) { |
| if (x >= nums.length) return 0; |
| |
| if (memo[x] != -1) return memo[x]; |
| |
| |
| int res = Math.max(nums[x] + rob(nums, x + 2, memo), rob(nums, x + 1, memo)); |
| memo[x] = res; |
| |
| return memo[x]; |
| } |
5.3、动态规划
| public static int rob(int[] nums) { |
| if (nums == null || nums.length == 0) return 0; |
| if (nums.length == 1) return nums[0]; |
| if (nums.length == 2) return Math.max(nums[0], nums[1]); |
| |
| int[] memo = new int[nums.length]; |
| memo[0] = nums[0]; |
| memo[1] = Math.max(nums[0], nums[1]); |
| |
| for (int i = 2; i < memo.length; i++) { |
| |
| memo[i] = Math.max(memo[i - 2] + nums[i], memo[i - 1]); |
| } |
| |
| return memo[memo.length - 1]; |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步