leetcode 377. 组合总和 Ⅳ

题目叙述

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
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)
请注意,顺序不同的序列被视作不同的组合。因此输出为 7。

分析

方法1 简单递归

直接使用进行递归求解。但是代码会超时。

class Solution {
public:
    int res = 0;
    int combinationSum4(vector<int>& nums, int target) {
        //使用深度搜索
        dfs(nums, target, 0);
        return res;
    }
    void dfs(vector<int>& nums, int target, int sum){
        if(sum>target) return;
        if(sum == target){
            res++;
            return;
        }
        for(int i=0;i<nums.size();i++){            
            dfs(nums, target, sum+nums[i]);
        }
    }
};

也可以如下图,自顶向下的递归。但是也是会超时。

class Solution {
public:
    int res = 0;
    //记忆化递归,使用一个数组来存储当target为i时所有的结果
    vector<int> dp;
    int combinationSum4(vector<int>& nums, int target) {
        //使用深度搜索
        dp.resize(target+1, 0);
        dfs(nums, target);
        return res;
    }
    void dfs(vector<int>& nums, int target){
        if(target<0) return;
        if(!target){
            res++;
            return;
        }
        for(int i=0;i<nums.size();i++){            
            dfs(nums, target-nums[i]);
        }
    }
};

在这里插入图片描述

方法2 记忆化递归

观察上图,由于存在很多重复的子问题,因此考虑使用记忆化递归。

class Solution {
public:
    //记忆化递归,使用一个数组来存储当target为i时所有的结果
    vector<unsigned long long> dp;
    int combinationSum4(vector<int>& nums, int target) {
        //先排个序
        sort(nums.begin(), nums.end());
        //使用深度搜索
        dp.resize(target+1, -1);//注意数组的初始化要初始化一个不可能的数,此处只能取负数,注意不能取0,因为dp[i]实际有可能取0,因此只能用负数表示还没有求解
        dfs(nums, target);
        return dp[target];
    }
    int dfs(vector<int>& nums, int target){
        if(!target){
            return 1;
        }
        if(dp[target]!=-1){//对于记忆化递归,当直接返回的时候,建议采用不等号,初始化为-1即一个不可能的数字,那么就表示没有被使用
            return dp[target];
        }
        int ans = 0;
        for(int i=0;i<nums.size();i++){
            if(target<nums[i]) break;
            ans += dfs(nums, target-nums[i]);          
            if(ans>0) dp[target] += ans;
            
        }
        dp[target] = ans;
        return dp[target];
    }
};
方法3 使用动态规划求解

常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。具体可以参考
在本题还应该注意的是,零钱兑换和求组合的情况总和很类似,但是两者不同的是零钱兑换需要的获取凑成指定target的所有的货币的组合的情况数目,这个组合的情况是没有排序的,即[1, 2, 3] 和[1, 3, 2]其实是一样的。但是针对该题组合的总和是排序的,即[1, 2, 3]和[1, 3, 2]是一样的。两者只需要进行交换内外循环的顺序。
对于兑换硬币:

for(int num:nums){
	for(int j =1;j<=target;j++)
		dp[j] += dp[j-num];
}

对于本题的组合问题

for(int j =1;j<=target;j++){
	for(int num:nums)
		dp[j] += dp[j-num];
}

仔细分析就会想到,第一种方式隐含了在取nums中的num元素的时候,位置就已经定死了,比如dp[6]可以由[1,2,3]构成,不能由[3, 2,1]构成,而这种限制在第二种内外循环下就不存在。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned long long> dp(target+1, 0);//此处如果不使用类型unsigned long long则会报溢出的错误
        dp[0] = 1;//注意这个一定要初始化为1而不是0
        for(int i=1;i<=target;i++){
            for(int j=0;j<nums.size();j++){//待选的不重复的数字
                if(i>=nums[j]) dp[i] += dp[i-nums[j]];//当i==nums[j]时,以nums[j]为结尾的所有排列就一个,所以可知直接使dp[0]=1实现这一目的
            }               
        }
        return dp[target];
    }
};

或者对于等于0的情况,单独讨论:

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned long long> dp(target+1, 0);//此处如果不使用类型unsigned long long则会报溢出的错误
        dp[0] = 0;//注意这个一定要初始化为1而不是0
        for(int i=1;i<=target;i++){
            for(int j=0;j<nums.size();j++){//待选的不重复的数字
                if(i>nums[j]) dp[i] += dp[i-nums[j]];//当i==nums[j]时,以nums[j]为结尾的所有排列就一个,所以可知直接使dp[0]=1实现这一目的
                if(i==nums[j]) dp[i] += 1;
            }               
        }
        return dp[target];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值