377号组合数问题

来源:9-7 面试中的0-1背包问题 Partition Equal Subset Sum

河水洗回忆

2018-02-28

老师您好!377号问题是求从数组中挑选一些数组成特定和的组合数,我不理解为什么把完全背包问题中的目标数放在外循环,数组中数放在内循环求出的就是包含重复的结果,可以给我解释一下么?

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int n=nums.size();
        if(n==0)
            return 0;
        
        vector<int> memo(target+1,0);
        memo[0]=1;
        for(int i=1;i<=target;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(nums[j]<=i)
                    memo[i]=memo[i]+memo[i-nums[j]];
            }
        }
        return memo[target];
    }
};


写回答

2回答

liuyubobobo

2018-02-28

如果数组中的数字放在外循环,相当于:已知用nums[0...i]这些数字凑成[0...target]的方法数,现在需要新考虑一个数字nums[i+1],现在,凑成[0...target]的方法有多少?此时,新考虑的这个nums[i+1]摆在哪里变成了问题。以凑成j为例,由于之前凑成j的方法完全没有用这个nums[i+1],所以nums[i+1]可以摆放在原先凑成j的所有方法的所有数字之间!(这还不考虑需要多个nums[i+1]的情况)。所以,所以,仅仅记住原先用nums[0...i]这些数字凑成j的方法数量是不够的,还需要具体是怎么凑出来的,才能找到摆放新的nums[i+1]的所有方法。


但是把目标数放在外循环,相当于每次需要考虑:用所有的nums数字凑成[0...j]的方法数已经知道了,现在要凑成j+1,多了几种方法?这时,用所有的nums凑成[0...j]的数量中,已经包含了不同顺序的结果,我们只需要考虑,在现有的所有凑成的方法后面,添加nums[0], nums[1], nums[2],... 能不能凑成j+1就好了。


这么说可能还是比较抽象。如果觉得自己理解的还不够透彻,我建议你用377号题目的例子:nums = [1, 2, 3],target = 4,走一遍上面的程序。i从1到4;j从0到2,一共循环才执行12次。每次更新memo[i]的时候,如果memo[i]变大了的话,都自己写出当前的memo[i]所记录的凑成数字i的方法数都是那几种(一共才7种),一定可以帮助你更深刻的理解这个问题。不要嫌麻烦!跟踪经典算法实现中的变量是如何变化的,想明白为什么会这么变化,和自己哪里理解的不一样,或者自己之前的理解漏掉了什么,是深入理解算法的非常有效的方法:)


如果在这个基础上,能够自己尝试换个方法编写,就更好了。比如自己实际思考一下,如果两层循环的顺序变了,在实现逻辑上到底有什么差别?有什么改变?多做这样的思考和实验,代码水平想不提高都难:)


加油!

0
5
liuyubobobo
回复
讲武德的年轻人
赞的。但是这个“直观的解释”更多地是在说明为什么把 target 放在外循环是正确的;而我在试图解释这两重循环顺序变化带来的程序逻辑的变化。继续加油!:)
2022-10-21
共5条回复

ShiveryMoon

2018-03-03

我正好也想问这个问题来着,内外层循环反过来居然就能从求组合变成求排列,我都傻了

0
0

玩转算法面试-- Leetcode真题分门别类讲解

课程配套大量BAT面试真题,高频算法题解析,强化训练

7410 学习 · 1150 问题

查看课程