只是不愿随波逐流 ...|

lidongdongdong~

园龄:2年7个月粉丝:14关注:8

8、动态规划基础

内容来自刘宇波老师玩转算法面试

动态规划解题套路框架
状态转移 + 重叠子问题 + 最优子结构(通过求子问题的最优解,可以获得原问题的最优解)
base case -> 状态 -> 选择 -> 定义 dp 数组 / 函数的含义
1、状态 = 变量
2、base case = 最基础的变量
3、如何选择 = 求最值 = 最优子结构

# 自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
for 选择 in 所有可能的选择:
# 此时的状态已经因为做了选择而改变
result = 求最值(result, dp(状态1, 状态2, ...))
return result
# 自底向上迭代的动态规划
# 初始化 base case
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

/**
* 斐波那契数列 Fibonacci Sequence
* F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2)
*/
public static int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}

1.2、重叠子问题

image
image

1.3、自顶向下解决问题「记忆化搜索」

/**
* 斐波那契数列 Fibonacci Sequence
* F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2)
*/
public static int fib(int n) {
int[] memo = new int[n + 1]; // memo[i] = fib(i)
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、自底向上解决问题「动态规划」

/**
* 斐波那契数列 Fibonacci Sequence
* F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2)
*/
public static int fib(int n) {
if (n <= 1) return n;
int[] memo = new int[n + 1]; // memo[i] = fib(i)
memo[0] = 0; // 0 -> 0
memo[1] = 1; // 1 -> 1
for (int i = 2; i <= n; i++) {
memo[i] = memo[i - 1] + memo[i - 2];
}
return memo[n];
}

1.5、降低空间复杂度

/**
* 斐波那契数列 Fibonacci Sequence
* F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2)
*/
public static int fib(int n) {
int[] memo = new int[2];
memo[0] = 0; // 0 -> 0
memo[1] = 1; // 1 -> 1
for (int i = 2; i <= n; i++) {
memo[i % 2] = memo[i % 2] + memo[(i - 1) % 2];
}
return memo[n % 2];
}

2、什么是「动态规划」

将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案

image

3、爬楼梯

70 - 爬楼梯

image

public static int climbStairs1(int n) {
int[] memo = new int[n + 1]; // memo[n] = climbStairs(n)
memo[0] = 1; // 0 -> 1
memo[1] = 1; // 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; // 0 -> 1
memo[1] = 1; // 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、图示

image
image

4.1.2、记忆化搜索

public class Solution {
private static int[] memo;
public static int integerBreak(int n) {
memo = new int[n + 1]; // memo[i] = integerBreak(i)
return breakInteger(n);
}
/**
* 将 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++) {
// i + (n - 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) {
// memo[i] 表示将数字 i 进行分割(至少分割两部分)后得到的最大乘积
int[] memo = new int[n + 1];
memo[2] = 1; // 2 -> 1 + 1
// memo[3] ... memo[n]
for (int i = 3; i <= n; i++) {
// 求解 memo[i]
int res = -1;
for (int j = 1; j <= i - 1; j++) {
// i = j + (i - 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++) {
// 求解 memo[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、图示

image
image
image

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);
}
/**
* 考虑偷取 [x ... n - 1] 范围里的房子
*/
private static int rob(int[] nums, int x, int[] memo) {
if (x >= nums.length) return 0;
if (memo[x] != -1) return memo[x];
// 选择 nums[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], 选择 nums[i] 要不要
memo[i] = Math.max(memo[i - 2] + nums[i], memo[i - 1]);
}
return memo[memo.length - 1];
}
posted @   lidongdongdong~  阅读(15)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开