跟着labuladong——动态规划核心套路详解

动态规划特点:

        题型:求最值

        核心:穷举

   三要素:

        1. 重叠子问题

        2. 状态转移方程(最关键)

        3. 最优子结构

解题套路(dong哥经验总结):

        0,明确base case

        1,明确 状态

        2,明确 选择

        3,明确dp函数/数组的定义

// 初始化 base case
dp[0][0][...] = base
// 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

509. 斐波那契数

暴力递归解法

    /* 暴力递归解法 */
    public int fib(int n) {
        // base case
        if (n == 0 || n == 1) {
            return n;
        }
        // 递推关系
        return fib(n - 1) + fib(n - 2);
    }

带备忘录的递归解法 :memo避免重复计算,自顶向下递归解法,自底向上回溯答案

    /* 带备忘录的递归解法 */
    public int fib(int n) {
        // 备忘录全初始化为 0
        int[] meno = new int[n + 1];
        // 进行带备忘录的递归
        return helper(meno, n);
    }

    private int helper(int[] meno, int n) {
        // base case
        if (n == 0 || n == 1) {
            return n;
        }
        // 已经计算过,不用再计算了
        if (meno[n] != 0) {
            return meno[n];
        }
        meno[n] = helper(meno, n - 1) + helper(meno, n - 2);
        return meno[n];
    }

dp数组的迭代解法:自底向上迭代解法

    /* dp 数组的迭代解法 */
    public int fib(int n) {
        if (n == 0) {
            return 0;
        }
        int[] dp = new int[n + 1];
        // base case
        dp[0] = 0;
        dp[1] = 1;
        // 状态转移
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

优化空间复杂度:时间复杂度O(N),空间复杂度O(1)

    /* 优化空间复杂度 */
    public int fib(int n) {
        // base case
        if (n == 0 || n == 1) {
            return n;
        }
        // 递推关系
        int prev = 0;
        int curr = 1;
        for (int i = 2; i <= n; i++) {
            int sum = prev + curr;
            prev = curr;
            curr = sum;
        }
        return curr;
    }

322. 零钱兑换

暴力解法:超时

    /* 暴力递归解法 */
    /**
     * 状态:目标金额 aomunt
     * 选择:coins 数组中列出的所有硬币面额
     * 函数定义:凑出总金额amount,至少需要coinChange(coins,amount)枚硬币
     * base case:aomount == 0时,需要0枚硬币;aomount < 0是,不可能凑出
     *
     * coinChange([1,2,5],11) 子问题+1
     * = 1+min(coinChange([1,2,5],10),coinChange([1,2,5],9),coinChange([1,2,5],6))
     */
     public int coinChange(int[] coins, int amount) {
        // base case
        if(amount == 0) {
            return 0;
        }
        if(amount < 0) {
            return -1;
        }
        int res = Integer.MAX_VALUE;
        for (int coin:coins){
            // 计算子问题的结果
            int subProblem = coinChange(coins,amount - coin);
            // 子问题无解则跳过
            if(subProblem == -1){
                continue;
            }
            // 在子问题中选择最优解,然后加一
            res = Math.min(res,subProblem+1);
        }
        return res == Integer.MAX_VALUE? -1:res;
     }

带备忘录递归解法:自顶向下递归解法O(coins.size() * amount)

    /**
     * 自顶向下递归解法
     */
    // 备忘录
    int[] memo;
    public int coinChange(int[] coins, int amount) {
        memo = new int[amount +1];
        // memo 数组全都初始化为特殊值
        Arrays.fill(memo,-666);
        return dp(coins,amount);
    }

    private int dp(int[] coins, int amount) {
        if (amount == 0) {
            return 0;
        }
        if (amount < 0) {
            return -1;
        }
        // 备忘录,防止重复计算
        if (memo[amount] != -666) {
            return memo[amount];
        }
        int res = Integer.MAX_VALUE;
        for (int coin : coins) {
            // 计算子问题的结果
            int subProblem = dp(coins, amount - coin);
            // 子问题无解则跳过
            if (subProblem == -1) {
                continue;
            }
            // 在子问题中选择最优解,然后加一
            res = Math.min(res, subProblem + 1);
        }
        memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
        return memo[amount];
    }
自底向上迭代解法:
    /**
     * 自底向上迭代解法
     */
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount +1];
        // dp 数组全都初始化为特殊值 amount+1
        Arrays.fill(dp,amount +1);

        // base case
        dp[0] = 0;
        // 外层for循环在遍历所有状态的所有取值
        for(int i = 0; i < dp.length; i++){
            // 内层 for 循环在求所有选择的最小值
            for(int coin : coins){
                // 子问题无解,跳过
                if((i - coin) < 0){
                    continue;
                }
                // 状态转移
                dp[i] = Math.min(dp[i], 1+dp[i - coin]);
            }
        }
        // 看看金额 amount 能不能凑出来
        return (dp[amount] == amount + 1) ? -1 : dp[amount];
    }

动态规划问题的本质:

1,如何穷举

写状态转移放错,暴力秋菊所有可行解

2,如何聪明的穷举

用备忘录消除重叠子问题,写出自顶向下的解法

进一步,可以写出自底向上的迭代解法

再进一步,可能可以优化控件复杂度

72. 编辑距离

    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        int[][] dp = new int[m + 1][n + 1];
        // base case
        for (int i = 1; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 1; j <= n; j++) {
            dp[0][j] = j;
        }
        // 穷举状态
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                // 状态转移
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
                }
            }
        }
        return dp[m][n];
    }



516. 最长回文子序列

    public int longestPalindromeSubseq(String s) {
        int[][] dp = new int[s.length()][s.length()];
        // base case
        for (int i = 0; i < s.length(); i++) {
            dp[i][i] = 1;
        }
        // 穷举状态
        for (int i = 1; i < s.length(); i++) {
            for (int j = i - 1; j >= 0; j--) {
                // 状态转移
                if (s.charAt(i) == s.charAt(j)) {
                    if (j == i - 1) {
                        dp[j][i] = 2;
                    } else {
                        dp[j][i] = dp[j + 1][i - 1] + 2;
                    }
                } else {
                    dp[j][i] = Math.max(dp[j + 1][i], dp[j][i - 1]);
                }
            }
        }
        return dp[0][s.length() - 1];
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值