【动态规划】02-关于内外层循环先遍历背包还是物品的问题

代码随想录:377. 组合总和 Ⅳ

文中提到了

如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

我们说的都是一维dp的内外层先遍历哪个的问题,基于此讨论。

nums = [1, 2, 3]
target = 4
所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

我们先来看常见的情况:

  • 外层for遍历物品,内层for循环遍历背包 (组合)
    dp[j - nums[i]] 如果外层i表示物品,内层j表示背包。
    物品就是从0-i。物品出现的顺序就只能是0-i的顺序。就是最外层循环i的时候,循环到1 就只有0,1这些物体,循环到2,这个时候是在0,1的基础上,再加2物体。也就是说,对于每次滚动跟新的过程,物品是随着更新的过程,慢慢被加进来的,每次都可见的是所有的背包容量(从0-j)。
    • 某一次滚动更新过程,它是固定i(物品种类),改变j(背包容量)
      它根据变化的j(背包容量),依次选择我当前放物品0,放物品1,...,放物品i,能到背包容量j的情况。
      只能选择已经遍历到的物品,比如i=1, 那就只能在物品0,物品1中选择,然后放入背包重量为0,背包重量为1,...,背包重量为j的情况。

再来看:

  • 外层for,i遍历背包,内层for,j循环遍历物品 (排列)
    dp[i - nums[j] 外层i表示背包,内层j表示物品。
    因为内层物品每次都会从0-j,所以在滚动更新的过程中,对于i=4,也就是背包重量确定为某一个值的时候,它会开始看有哪些物品可以满足背包的条件(此时要求背包能背的重量为1),它在一次更新过程是能看到所有的物品的。这也是为啥说它与排列更像,因为排列本身就是要满足条件的物品中的所有物品参与进来。
    • 某一次滚动更新过程,它是固定i(背包容量),改变j(物品种类), 也就是
      dp[4] = dp[4-nums[0]] + dp[4-nums[1]] + dp[4-nums[2]], 也就是
      dp[4] = dp[3] + dp[2] + dp[1]
      这里的dp[3],就是我准备放nums[0]了,那你要给我腾出nums[0]的位置,那就是dp[3], 在这个基础上,再加一个nums[0] (1) 就得到target 4了
      这里仔细感受一下,是不是就是它根据变化的j(物品种类),依次选择我当前放物品0,放物品1,...,放物品j,能到背包容量i的情况。
      image
class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 0; i <= target; i++) {  // 外层i循环遍历背包
            for (int j = 0; j < nums.length; j++) {  // 内层j循环遍历物品
                if (i >= nums[j]) {
                    dp[i] += dp[i - nums[j]]; // 依次选择当前放物品j
                }
            }
        }
        return dp[target];
    }
}

总结

  • 对于外层for循环遍历物品,内层for遍历背包的情况
    在一次滚动更新的过程中(此时滚动的是物品的种类),它考虑了全部的背包重量,适用于组合这种,元素位置不同,算同一情况的,也就是不管元素位置怎么样,只统计一遍,那我按照物品遍历的顺序,只是其中的一种,但是也是组合了。

  • 对于外层for遍历背包,内层for循环遍历物品的情况
    在一次滚动更新的过程中(此时滚动的是背包的容量),它考虑了全部的物品种类,适用于排列这种,元素位置不同,算不同情况的。

posted @ 2024-11-20 17:06  chendsome  阅读(89)  评论(0)    收藏  举报