Leetcode算法学习日志-39 Combination Sum

本文介绍了LeetCode第39题Combination Sum的解题思路。题目要求找到一组候选数字的所有独特组合,使得它们的和等于目标值。使用回溯法进行搜索,允许每个数字无限次重复选择。文章详细解析了回溯法的基本步骤,并提供了C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Leetcode 39 Combination Sum

题目原文

Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

For example, given candidate set [2, 3, 6, 7] and target 7,
A solution set is:

[
  [7],
  [2, 2, 3]
]

题意分析

给一组候选数据集合以及一个目标,求候选集合中的不同组合,使得每种组合的和等于目标值。候选集合中没有重复的数,每种组合中可以重复选择同一个候选数,同一组合的不同排列算一种组合。

解法分析

五大常用算法思想为分治、贪心、动态规划、回溯、分支界限。本题采用回溯思想,下面先对回溯法进行简要介绍。

回溯法被称为“通用解题法”,用它可以系统地搜索所有解,它是一个系统性又具跳跃性的搜索算法,是暴力搜索的很大优化。回溯法的主要步骤如下:

  • 针对所给问题,定义问题的解空间。

本题的解空间就是不同候选数组合的集合,注意同一个数可以重复出现。

  • 确定易于搜索的解空间结构

通常解空间都是树的结构,一个是子集树,二是排列树,子集树用于解是一个集合的子集的情况,用0表示集合中一个元素没有出现在解中,用1表示出现在解中,由此构成一个二叉树,深度为集合的大小,叶子节点有2^n个,代表2^n个解;排列树用于解是集合的不同排列的情况,此时树就为n叉树,深度为集合的大小,叶子结点n!个。本题情况特殊,由于同一个元素可以出现多次,所以该解空间数不是二叉树,且深度不限于候选集合大小。

  • 以深度优先搜索的方式搜索解空间树,并在搜索过程中用剪枝函数避免无效搜索

剪枝函数包括约束函数,如扩展节点层数到达n等;界限函数,如有的子树注定达不到最优解,就提前剪去。本题没有深度限制,约束为加上扩展节点后不能超过目标值,界限为(Target-sum)<minCandi,此时就进入同级的下一个子树。C++代码如下:

class Solution {
private:
    vector<int> candi;//use this varibles to avoid parameter trans to recursive function backtracking
    int n;
    int Target;
    int s=0;
    int sum=0;
    //int minCandi;//minValue of candidates
    vector<int> temp;
    vector<vector<int>> solutionSet;
public:
    void backtracking(int t){
        if(sum==Target){
             solutionSet.push_back(temp);
            return ;
            
        }
           
        for(int i=s;i<n;i++){
           // cout<<"this layer"<<t<<" "<<"i="<<i<<" "<<"sum="<<sum<<" "<<endl;
            s=i;//to avoid same solution
            if((sum+candi[i])>Target)
                continue;
            else{
                sum+=candi[i];
                //cout<<sum<<endl;
                temp.push_back(candi[i]);
                backtracking(++t);
                temp.pop_back();
                sum-=candi[i];
                
            }
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        Target=target;
        candi=candidates;
        //minCandi=candi[0];
       /* for(auto cc:candi)
            minCandi=min(minCandi,cc);*/
        n=candi.size();
        backtracking(1);
        return solutionSet;       
    }
};
代码中回溯法用递归函数backtracking实现,为了避免给递归函数传递过多参数,可以将参数设定为类的变量。为了避免产生同一种组合的不同排列,在进行树搜索时,定义一个变量s,用于存储子树树根所选元素,其所有子树选择元素必须大于等于它。回溯的关键点就在

   temp.push_back(candi[i]);
                backtracking(++t);
                temp.pop_back();
                sum-=candi[i];
在找到解或没找到解回溯后,应将所选值pop掉,sum也得恢复成上一级状态。由于一个数不满足界限条件,即加上它之后超过target,则比它大的同级元素也不同再搜索了,由此可大大减小搜索次数,因此算法的改进版本如下:

class Solution {
private:
    vector<int> candi;//use this varibles to avoid parameter trans to recursive function backtracking
    int n;
    int Target;
    int s=0;
    int sum=0;
    int minCandi;//minValue of candidates
    vector<int> temp;
    vector<vector<int>> solutionSet;
public:
    void backtracking(int t){
       /* if(sum==Target){
             solutionSet.push_back(temp);
            return ;
            
        }*/
           
        for(int i=s;i<n;i++){
           // cout<<"this layer"<<t<<" "<<"i="<<i<<" "<<"sum="<<sum<<" "<<endl;
            s=i;//to avoid same solution
            if((sum+candi[i])==Target){
                temp.push_back(candi[i]);
                solutionSet.push_back(temp);
                temp.pop_back();
                return;//如果candidate从小到大,则这一层调用有解,后面也不用遍历了
            }
            else if((Target-sum-candi[i])<minCandi||(sum+candi[i])>Target)
                continue;
            else{
                sum+=candi[i];
                //cout<<sum<<endl;
                temp.push_back(candi[i]);
                backtracking(++t);
                temp.pop_back();
                sum-=candi[i];
                
            }
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        Target=target;
        sort(candidates.begin(),candidates.end());
        candi=candidates;
        minCandi=candi[0];
        /*for(auto cc:candi)
            minCandi=min(minCandi,cc);*/
        n=candi.size();
        backtracking(1);
        return solutionSet;       
    }
};
先对candidate进行由小到大排序,这样就能大大减小搜索次数,注意在判断和等于target时要先push再pop,确保每一次push都对应一次pop。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值