0
点赞
收藏
分享

微信扫一扫

动态规划学习心得分享

最近在代码随想录(代码随想录)刷了一些有关动态规划的算法题,收获还蛮大的,下面是我的一些学习心得分享,不足之处敬请批评指正~

首先来简单介绍一下什么是动态规划以及动归与贪心有何区别?

动态规划的题型可以分为几类:(我暂时只刷到了完全背包问题)

0-1背包和完全背包有何区别呢?

动态规划类问题的一般解题步骤如下:

下面我以一道经典的整数拆分问题为例演示上述过程:力扣

下面直接以代码说明问题:

class Solution {
//整数拆分问题
    public int integerBreak(int n) {
//定义dp数组,明确数组中每个元素的含义:dp[i]表示将i拆分后所得乘积的最大值
//数组长度一般定义为n+1,因为我们想要的结果就是dp[n]
        int[] dp = new int[n + 1];
//初始化dp数组,值为2时,所得最大拆分结果为1,因为值为0或1时的初始化没有意义所以在这里就不初始化了
        dp[2] = 1;
//外层循环
        for (int i = 3; i <= n; i++) {
//内层循环
            for (int j = 1; j < i - 1; j++) {
//递推公式,dp[i]用以在每次循环时和之前的值进行比较,(i-j)*j表示为拆成两个数所得乘积
//j*dp[i-j]表示将i拆分成两个及两个以上所得乘积(利用之前计算结果),递归顺序为从前到后
//递推公式不太理解的同学自己在草稿纸上推导一下就明白了
                dp[i] = Math.max(dp[i], Math.max((i - j) * j, j * dp[i - j]));
            }
        }
//返回结果
        return dp[n];
    }
}

相信大家已经对动归有了一些认识,接下来我们来看看经典的0-1背包问题:

0-1背包问题

提前敲小黑板:0-1背包问题要注意的点是外层循环和内层循环的遍历顺序

下面以代码说明问题:

public class packBag {
    public static void testWeightBagProblem(int[] weight, int[] value, int size) {
        int wLen = weight.length;
        //定义dp数组,dp[j]表示背包容量为j时能获得的最大价值
        int[] dp = new int[size + 1];
        //遍历顺序:先遍历物品再遍历背包容量
        for (int i = 0; i < wLen; i++) {
            //倒序遍历商品容量,防止物品被重复放入i
            //一维数组必须先遍历商品再遍历背包容量
            //这里的终止条件为j>=weight[i],防止为装不下的物品初始化,且防止下标越界
            for (int j = size; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //打印dp数组
        for (int i = 0; i <= size; i++) {
            System.out.print(dp[i] + " ");
        }
    }
}

 下面是0-1背包的经典的变种问题-统计满足条件的组合个数:

 直接上代码:

 public static int findTargetSumWays(int[] nums, int target) {
        //数学推导
        //left + right = sum
        //left - right = target
        //left = (sum + target) / 2
        //如果sum + target的和不为偶数则无解
        int sum = 0;
        for (int i = 0; i < nums.length; i++) sum += nums[i];
        if (Math.abs(target) > sum) return 0;
        if ((target + sum) % 2 != 0) return 0;
        int size = (target + sum) / 2;
        //dp数组的定义:dp[j]表示:和为j的组合有多少种
        //找出和为size的组合的个数即dp[size]
        int[] dp = new int[size + 1];
        dp[0] = 1;
//依然是先遍历商品后遍历背包容量
        for (int i = 0; i < nums.length; i++) {
            for (int j = size; j >= nums[i]; j--) {
//这里是重点!!!!
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[size];
    }

下面我们进行一个总结:

1.0-1背包问题中最重要的点是弄清楚背包容量是多少,物品是哪些,实际问题中可能不会直接告诉你这些信息,我们要能从题干中分析出来

2.其次,我们要注意遍历顺序为先遍历物品后遍历背包容量

3.最后,我们要注意题目中要统计的是有关最大价值类问题还是组合数的问题。

如果是最大价值类问题递推公式就是:

   dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);

如果是组合数的问题递推公式就是:

 dp[j] += dp[j - nums[i]];

 完全背包问题:

再次提前敲响小黑板:要关注代码中内层循环的遍历顺序问题和递推公式问题以及区分组合问题和排列问题 

依旧以代码说明问题:

public class CompletePack {
    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWeight = 4;
        int[] dp = new int[bagWeight + 1];
//遍历物品
        for (int i = 0; i < weight.length; i++) { 
//注意这里,如果是完全背包问题就是从前到后遍历背包容量
            for (int j = weight[i]; j <= bagWeight; j++) {
//递推公式和0-1背包相同
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        System.out.println(dp[bagWeight]);
    }
}

 下面是完全背包问题的变种问题:

 这道题就是很明显的完全背包的组合问题,直接上代码:

    public int change(int amount, int[] coins) {
        int len = coins.length;
        //定义dp数组,大小为背包容量大小->amount
        //dp[i]表示背包容量为i时的组合数
        int[] dp = new int[amount + 1];
        //初始化
        dp[0] = 1;
        //先遍历商品,再遍历背包容量
        for (int i = 0; i < len; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                    dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }

其实这个问题就是将组合问题的递推公式和完全背包问题的内层循环的遍历顺序相结合

再来一道有关完全背包的排序问题:

 这道题要区分相同组合的不同排列问题,直接上代码:

排列问题和组合问题的最大区别是:排列问题要先遍历背包容量再遍历物品!!!

    public int combinationSum4(int[] nums, int target) {
        //求排列就要先遍历背包容量后遍历商品
        int len = nums.length;
        int[] dp = new int[target + 1];
        //dp数组初始化
        dp[0] = 1;
//注意这里,如果是排列问题就要先遍历背包容量,再遍历物品!!!
        for (int i = 0; i <= target; i++) {
            for (int j = 0; j < len; j++) {
                if (i >= nums[j])
//递推公式依旧没有区别-求数量的递推公式
                dp[i] += dp[i - nums[j]];
            }
        }
        return dp[target];
    }

最后再来分享一道题力扣

这道题其实是完全背包问题和0-1背包问题中求最大价值类问题的结合体

直接上代码进行分析: 

    public static int coinChange(int[] coins, int amount) {
        int max = Integer.MAX_VALUE;
        //dp[i]表示背包容量为i所需物品的最少个数为dp[i]
        int[] dp = new int[amount + 1];
        //dp数组初始化为最大值
        for (int i = 0; i < dp.length; i++) {
            dp[i] = max;
        }
        //当金额为0时需要的硬币数目为0
        dp[0] = 0;
        //可以把需要的硬币数量看作背包的价值,求最少的硬币数量即为最小价值
        //每一枚硬币的价值为1(默认)
        //组合问题先遍历物品
        for (int i = 0; i < coins.length; i++) {
            //完全背包问题,为正序遍历
            for (int j = coins[i]; j <= amount; j++) {
                if (dp[j - coins[i]] != max)
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
            }
        }
        //判断,如果没有硬币的组合满足amount,则返回-1
        return dp[amount] == max ? -1 : dp[amount];
    }
举报

相关推荐

0 条评论