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 @ 2023-05-25 12:52  lidongdongdong~  阅读(8)  评论(0编辑  收藏  举报