聊一聊动态规划

163 阅读2分钟

我报名参加金石计划一期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

什么是动态规划

动态规划是一种解决问题的思想。

要想解决目标问题A,可以先解决问题B。

B问题和A问题是相似的,区别只在于B问题的入参规模小。

B问题的解决有助于A问题的解决。

举个例子

斐波那契数列。

第一个数和第二个数都是是1,后面的数都是前两个数的和,求第n个数是多少?

1 1 2 3 5 8 13

可以先求出第三个数,进而求第四个数,最后解决第n个数。

    public int fib(int n) {
        int[] dp = new int[n + 2];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

动态规划的关键点是状态的定义和状态转移方程。

状态定义: dp[i] 代表第i个数。

状态转移方程: dp[i] = dp[i-1] + dp[i-2]

再看个例子。

零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

看示例1。

思路:

目标问题是凑成面值11所需要的最少硬币个数。

先考虑凑成面值1的、继而2的、3的、直至最终目标11

凑成面值为1:取面值为1的硬币1个

凑成面值为2:取面值为2的硬币1个

凑成面值为3:如果最后一枚的硬币面值是1,凑成面值为2的结果+1是其中一种情况

如果最后一枚的硬币面值是2,凑成面值为1的结果+1是其中一种情况

......

枚举最后一枚硬币的面值,结合之前的答案,能很快找出最小的次数

状态定义:dp[i] 为凑成金额i的最少硬币个数

状态转移方程:

dp[i]=minj=2,3,5dp[ij]+1dp[i] = \min_{j=2,3,5} dp[i-j]+1

j为硬币面值枚举

    public int coinChange(int[] coins, int amount) {
        int max = amount + 1;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, max);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if (coins[j] <= i) {
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }

\