组合总和问题---一个数字可重复选择多次

本文详细解析了LeetCode上的经典题目“组合总和”(Combination Sum),介绍了问题背景、核心算法思路及其实现过程。通过深度优先搜索(DFS)策略寻找所有可能的组合,并附带了C语言的完整代码实现。

leetcode 39. Combination Sum

一、问题描述

    给定一组数(数字没有重复)和一个目标值,找到数字中和为目标值的所有唯一组合。一个数字可以重复选择多次。
  【注意】

  •         所有数字(包括目标)都是正整数。
  •         结果集不能包含重复的组合。
【举例】
例1:输入: candidates = [2,3,6,7], target = 7,
输出解集:
[
  [7],
  [2,2,3]
]
例2:输入: candidates = [2,3,5], target = 8,
输出解集:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

二、问题分析

由于要输出所有解集具体组合而不是解集个数,因此采用dfs的方法,边递归边记录结果

三、算法实现

/*********************************************************
Author:tmw
date:2018-5-14
*********************************************************/
#include <stdio.h>
#include <stdlib.h>

/**
    *final_r_perLen :存储每一组最终结果的长度
    **final_r       :存储所有结果的数组
    start           : 当前递归起始位置
**/
#define MAX_SIZE 200
int result[MAX_SIZE] = {0}; /**存储中间结果的数组**/
int j=0;                    /**结果数组下标**/
int count = 0;              /**记录所有可能的组合个数**/
void sum_dfs( int* array, int array_len, int sum, int start, int** final_r, int* final_r_perLen )
{
    /**终止条件**/
    if( sum == 0 )
    {
        int i;
        for( i=0; i<j; i++ )
            final_r[count][i] = result[i];

        final_r_perLen[count] = j;
        count++;
        return;
    }

    int k;
    for( k=start; k<array_len; k++ )
    {
        /**收敛条件---剪枝**/
        if( array[k]>sum ) return;

        /**将当前元素加入中间结果数组**/
        result[j++] = array[k];
        sum_dfs(array,array_len,sum-array[k],k,final_r,final_r_perLen);

        j--; /**j回退到递归之前的状态**/
    }
}

/**快排**/
#define swap( x,y,t ) (t=x,x=y,y=t)
int fast_sort_one( int* array, int low, int high )
{
    int target = array[low];
    int temp;
    while( low < high )
    {
        if( low<high && array[high]>=target )
            high--;
        swap(array[low],array[high],temp);
        if( low<high && array[low]<=target )
            low++;
        swap(array[low],array[high],temp);
    }
    return low;
}
void fast_sort_all( int* array, int low, int high )
{
    if( low<high )
    {
        int mid_index = fast_sort_one(array,low,high);
        fast_sort_all(array,low,mid_index-1);
        fast_sort_all(array,mid_index+1,high);
    }
}

int** combinationSum(int* candidates, int candidatesSize, int target, int** columnSizes, int* returnSize)
{
    printf("%d\n",candidatesSize);
    fast_sort_all(candidates,0,candidatesSize-1);
    int i;

    int* final_r_perLen = (int*)malloc(MAX_SIZE*sizeof(int));
    int** final_r = (int**)malloc(MAX_SIZE*sizeof(int*));

    for( i=0; i<MAX_SIZE; i++ )
        final_r[i] = (int*)malloc(MAX_SIZE*sizeof(int));

    count = 0; /**全局变量count清零**/
    sum_dfs(candidates,candidatesSize,target,0,final_r,final_r_perLen);

    *columnSizes = (int*)malloc(count*sizeof(int));
    for(i=0; i<count; i++)
        (*columnSizes)[i] = final_r_perLen[i];
    *returnSize = count;

    return final_r;
}

四、执行结果

leetcode accept


梦想还是要有的,万一实现了呢~~~~ヾ(◍°∇°◍)ノ゙~~~

<think>好的,我现在需要帮用户解决用C++实现数字组合问题的递归解决方案。首先,我得理解用户的具体需求是什么。用户提到了数字组合问题,但没有具体说明是什么样的组合,比如是排列还是组合,或者有其他限制条件。根据提供的引用内容,用户可能是指类似全排列或者特定约束下的组合生成,比如不重复的组合,或者总和固定的组合。 接下来,我需要查看用户提供的引用内容,看看是否有相关的例子可以借鉴。引用[1]中的代码是一个组合型枚举的DFS实现,输入n和m,输出从n个中选m个的所有组合。引用[2]中的代码处理的是递推问题,涉及三种格子的排列组合。引用[3]和[4]则提到了递归在排列组合中的应用,特别是引用[4]中的代码似乎处理的是将数字n分解为不同组合的方式,有一定的约束条件。 用户的问题可能类似于这些引用中的情况,但需要具体明确。比如,用户可能想要生成所有可能的数字组合,这些组合满足某些条件,如和为定值,或者不重复选择等。根据常见的数字组合问题,比如“给定一个数组一个目标找出所有唯一组合使数字和等于目标”,或者“从1到n中选k个的所有组合”。但用户没有具体说明,可能需要进一步假设。 考虑到引用[1]中的组合枚举代码,用户可能已经了解组合生成的递归方法,但需要其他类型的组合问题解决方案,比如允许重复或不同约束的情况。或者用户可能需要将数字组合特定形式,比如分解成不同的和,如引用[4]中的例子。 为了确保正确,我需要先明确数字组合问题的定义。比如,假设问题是:给定一个n,找出所有可能的组合,使得这些组合中的之和等于n,并且每个都不重复,或者允许重复。或者,可能是从一组数字选择若干个,使得满足某种条件,如和为定值。 假设用户的问题类似于组合求和,即给定一个目标找出所有可能的组合使其和为目标,且数字来自给定集合,允许或不允许重复。例如,LeetCode上的组合总和问题。这种情况下,递归解决方案通常使用回溯法,通过递归尝试每个可能的数字,并在满足条件时记录结果。 但根据用户提供的引用,特别是引用[4]中的代码,可能用户的问题更接近将n分解为不同的之和,如整划分问题。例如,将n分解为若干个正整之和,且后面的不大于前面的,以避免重复组合。这种情况下,递归函需要考虑当前分解的不能超过之前的值,从而避免重复排列。 例如,引用[4]中的代码,函f(n, bound)计算将n分解为多个之和的方式,其中每个不超过bound,且分解后的满足n-k >=1且n-k <=k。这可能是一个特定约束的整划分问题。但用户可能需要更通用的解决方案,生成所有可能的组合,而不仅仅是计算方式。 因此,我需要结合用户提供的引用,特别是引用[1]的组合枚举代码,设计一个递归的C++解决方案,生成所有满足特定条件的数字组合。例如,生成所有从1到n中选k个组合,或者生成所有可能的和为n的组合,允许或不允许重复使用数字。 假设用户的问题是从1到n中选取m个的所有组合,如引用[1]中的代码所示,但用户可能希望更一般的数字组合问题,比如允许重复或不同的约束。或者,用户可能需要解决类似整划分的问题,将n分解为不同的之和,并生成所有可能的组合。 我需要明确用户的具体需求,但根据提供的引用,可能用户需要的是生成所有可能的组合,例如,给定一个数组,生成所有子集,或者生成所有排列,或者生成满足特定条件的组合。例如,引用[3]中提到的递归求解数字的排列组合。 因此,可能的解决方案需要根据具体问题来设计。假设用户的问题是需要生成所有可能的k个组合,类似组合型枚举,但使用递归方法。或者,生成所有排列,如全排列。 根据引用[1]中的代码,组合枚举的递归实现已经存在,用户可能需要类似的代码,或者更复杂的组合问题。比如,允许数字重复,或者不同的选择方式。 或者,用户的问题可能类似于整划分,例如将数字n分解为若干正整之和,并列出所有可能的分解方式,这需要不同的递归结构,确保分解不重复,例如保持分解后的按非递增顺序排列,以避免重复。 例如,对于分解n=4,可能的分解为4, 3+1, 2+2, 2+1+1, 1+1+1+1等,但根据不同的约束条件,可能有不同的结果。 在这种情况下,递归函需要跟踪当前的分解路径,并且确保每一步选择不超过前一个,以避免重复组合。 假设用户的问题属于这种情况,那么代码的结构可能如下: void dfs(int target, int start, vector<int>& path) { if (target == 0) { 输出或保存path; return; } for (int i = start; i <= target; ++i) { path.push_back(i); dfs(target - i, i, path); // 允许重复,例如分解为2+2 path.pop_back(); } } 这样,当调用dfs(n, 1, path)时,会生成所有可能的分解方式,其中每个至少为start,且后面的不小于前面的,以避免重复。例如,分解4为1+1+1+1, 1+1+2, 1+3, 2+2, 4等。 但根据不同的需求,可能需要调整递归的条件。例如,如果不允许重复使用相同的数字,则需要调整循环中的起始值。 或者,如果问题是从给定数组选择数字,使其和为特定值,则需要不同的处理方式,比如回溯法,每次选择是否包含当前数字,并递归处理剩下的数字。 但根据用户提供的引用,尤其是引用[1]的组合枚举,可能用户的问题更接近生成所有k个组合,或者生成所有可能的分解方式。 现在,我需要参考用户提供的引用中的代码结构,比如引用[1]的代码使用DFS递归生成组合,参包括当前的位置x和起始值s,避免重复组合。 因此,可能的解决方案是类似的递归结构,根据具体问题进行调整。 例如,用户可能的问题是从n个数字选择m个组合,那么引用[1]的代码已经解决了这个问题,但用户可能需要不同的约束,比如允许重复,或者数字之和等于某个值。 或者,用户可能需要解决组合总和问题,即从给定数字中选取若干个,使得它们的和等于目标,允许重复或不重复。 此时,需要根据具体问题设计递归函。例如,对于允许重复的组合总和问题,递归函可以尝试每个数字,然后递归处理剩余的目标值,同时允许再次选择数字。 例如: void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& result) { if (target == 0) { result.push_back(path); return; } for (int i = start; i < candidates.size(); ++i) { if (candidates[i] > target) continue; path.push_back(candidates[i]); backtrack(candidates, target - candidates[i], i, path, result); // 允许重复,传入i而不是i+1 path.pop_back(); } } 但用户的问题是否属于这种情况还不清楚。需要进一步分析。 另外,用户提到的“数字组合问题”可能是指生成所有可能的排列或组合,例如排列型枚举,如引用[2]中的全排列。 不过,引用[1]中的代码是组合型枚举,而引用[2]中的代码是递推求解方案,而非生成具体的组合。 因此,用户可能需要生成所有可能的数字组合,满足某种条件,比如和为定值,或者排列组合。 结合用户提供的引用,可能用户需要的是类似组合型或排列型枚举的递归解决方案,但具体问题可能不同。 例如,用户可能的问题类似于:给定数字n和k,生成所有可能的k个组合,使得它们的和等于n,每个可以重复使用或不可重复使用。 或者,用户的问题可能更简单,如生成所有可能的子集,或者生成排列。 但根据用户给出的引用,特别是引用[1]的组合枚举代码和引用[4]的递归分解代码,可能用户需要的是类似组合枚举或整分解的问题的递归解决方案。 为了确保正确,我需要假设用户的问题类似于组合枚举,但可能允许不同的约束,例如允许重复选择,或者总和条件。 假设用户的问题是从1到n中选择若干个,使得它们的和等于s,并且每个只能选一次,那么递归的解决方案需要遍历所有可能的组合,检查它们的和是否等于s。 或者,用户的问题可能不需要总和条件,只是生成所有可能的组合,如从n个中选m个的组合,这已经由引用[1]的代码解决,但用户可能希望得到不同的实现方式。 因此,我需要进一步明确问题。但根据用户的问题描述:“数字组合问题”,可能指的是生成所有可能的组合,而不是排列。例如,组合不考虑顺序,而排列考虑顺序。 例如,组合问题:从n个元素中选k个,不考虑顺序,如引用[1]中的代码;排列问题:考虑顺序,如全排列。 根据引用[3]中的描述,用户可能想用递归来求解排列组合问题,所以可能用户需要的是排列或组合的生成。 现在,我需要根据用户提供的引用中的代码,特别是引用[1]的组合枚举,来构造一个递归解决方案。 例如,用户可能希望解决类似的问题,但可能条件不同,比如允许重复,或者数字来自特定集合。 或者,用户可能需要解决类似整划分的问题,如将n分解为若干之和的所有可能方式。 例如,对于n=4,可能的组合包括:4, 3+1, 2+2, 2+1+1, 1+1+1+1等,这种情况下,递归函需要确保组合中的按非递增顺序排列,以避免重复。 这种情况下,递归函的参可能包括当前剩余值、当前组合中的最大值(避免选择更大的导致重复),以及当前的路径。 例如,函定义可能如下: void dfs(int remaining, int max_num, vector<int>& path, vector<vector<int>>& result) { if (remaining == 0) { result.push_back(path); return; } for (int i = max_num; i >= 1; --i) { if (i > remaining) continue; path.push_back(i); dfs(remaining - i, i, path, result); path.pop_back(); } } 这样,调用dfs(n, n, path, result)将生成所有可能的分解方式,按非递增顺序排列,避免重复。 例如,n=4时,生成4, 3+1, 2+2, 2+1+1, 1+1+1+1。 但用户的问题是否属于这种情况还不确定。可能需要更多的信息,但根据用户提供的引用,特别是引用[4]中的代码,处理的是分解问题,可能用户需要类似的解决方案。 因此,我需要结合这些信息,给出一个递归的C++解决方案,可能针对整划分或组合生成问题。 假设用户的问题是将数字n分解为若干正整之和,并生成所有可能的组合,每个组合中的按非递增顺序排列,以避免重复。例如,n=4的输出包括4, 3+1, 2+2, 2+1+1, 1+1+1+1。 这种情况下,递归函的实现可能如下: #include <iostream> #include <vector> using namespace std; void findCombinations(int remaining, int maxAllowed, vector<int>& current, vector<vector<int>>& result) { if (remaining == 0) { result.push_back(current); return; } for (int i = maxAllowed; i >= 1; i--) { if (i > remaining) continue; current.push_back(i); findCombinations(remaining - i, i, current, result); current.pop_back(); } } int main() { int n; cout << "请输入目标n: "; cin >> n; vector<vector<int>> result; vector<int> current; findCombinations(n, n, current, result); cout << "所有可能的组合为:" << endl; for (auto& combination : result) { for (size_t j = 0; j < combination.size(); j++) { cout << combination[j]; if (j != combination.size() - 1) cout << "+"; } cout << endl; } return 0; } 这段代码将生成所有可能的分解方式,每个组合中的按非递增顺序排列,避免重复。 但用户可能需要不同的约束条件,例如不允许重复使用相同的数字,或者允许排列顺序不同视为不同的组合。需要进一步确认。 假设用户的问题是从一组数字选择k个组合,比如引用[1]中的问题,输入n和m,输出所有m个组合。这种情况下,用户提供的引用[1]中的代码已经解决了这个问题,但用户可能需要不同的实现方式或其他变种。 或者,用户的问题可能类似于生成所有子集,比如给定一个数组,生成所有可能的子集,这种情况下,递归方法需要遍历每个元素选择是否包含它。 例如: void generateSubsets(vector<int>& nums, int index, vector<int>& current, vector<vector<int>>& result) { if (index == nums.size()) { result.push_back(current); return; } // 不包含当前元素 generateSubsets(nums, index + 1, current, result); // 包含当前元素 current.push_back(nums[index]); generateSubsets(nums, index + 1, current, result); current.pop_back(); } 但根据用户提供的引用,可能更接近组合枚举或整分解问题。 因此,我需要根据常见的数字组合问题,结合用户提供的引用,给出一个递归解决方案。例如,组合枚举(从n个中选m个)的递归实现,或者整划分的组合生成。 考虑到用户提供的引用[1]中的代码是组合枚举,可能用户需要的是类似的解决方案,但可能需要不同的解释或不同的条件。 因此,总结用户的可能需求,给出一个递归的C++解决方案,例如组合枚举,并解释其工作原理。例如,输入n和m,输出所有m个组合,每个组合中的元素按升序排列,不重复。 用户提供的引用[1]中的代码已经实现了这一点,但用户可能需要解释或不同的实现。或者,用户可能希望允许重复选择元素,例如组合中的元素可以重复出现,但顺序不计。 例如,从n个元素中选m个,允许重复,但组合不考虑顺序。比如,n=3, m=2,可能的组合是(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)。 这种情况下,递归函需要调整起始条件,允许当前元素被重复选择,但确保组合中的元素非递减排列,避免重复。 例如,递归函的参包括当前位置、起始值,每次递归从当前元素开始,而不是下一个元素。 例如: void dfs(int x, int start) { if (x > m) { 输出组合; return; } for (int i = start; i <= n; i++) { st[x] = i; dfs(x + 1, i); // 允许重复,起始值为i } } 这样,当调用dfs(1, 1)时,每个位置可以选择当前起始值或更大的值,允许重复。 例如,n=3, m=2,将生成(1,1), (1,2), (1,3), (2,2), (2,3), (3,3)。 因此,用户可能需要这样的代码,根据不同的组合条件调整递归参。 结合用户的问题描述和提供的引用,我认为用户需要的是递归解决组合问题的C++代码,可能类似于组合枚举或整分解。因此,我需要提供相应的代码和解释,并确保符合用户提供的引用中的结构。 例如,如果用户的问题是从n个中选m个的组合,允许重复,那么递归实现如下: #include <iostream> #include <vector> using namespace std; int n, m; vector<int> combination; void dfs(int start, int depth) { if (depth == m) { for (int num : combination) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= n; ++i) { combination.push_back(i); dfs(i, depth + 1); // 允许重复,起始值保持为i combination.pop_back(); } } int main() { cin >> n >> m; dfs(1, 0); return 0; } 这样,输入n=3,m=2,输出允许重复的组合。 但用户的具体需求可能不同,可能需要不同的约束条件。例如,不允许重复,此时起始值为i+1,如引用[1]中的代码。 如果用户的问题是不允许重复的组合,即从n个不同的选择m个,不允许重复,那么引用[1]中的代码已经解决了这个问题。用户可能需要确认这一点。 因此,用户可能需要的是允许重复的组合生成,或者整分解的组合生成。 根据用户提供的引用[4],其中处理的是分解问题,可能用户需要的是将数字n分解为若干之和的递归解决方案,例如: #include <iostream> #include <vector> using namespace std; void findCombinations(int target, int current, vector<int>& path, int start) { if (current == target) { for (int num : path) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= target - current; ++i) { path.push_back(i); findCombinations(target, current + i, path, i); // 允许重复,起始值为i path.pop_back(); } } int main() { int n; cin >> n; vector<int> path; findCombinations(n, 0, path, 1); return 0; } 这段代码将生成所有可能的组合,其中的和等于n,允许重复,并且组合中的按非递减顺序排列,以避免重复。例如,n=3时,输出1 1 1;1 2;2 1(但由于起始值控制,实际输出可能不同,需要调整递归参确保顺序)。 不过,可能更准确的实现需要确保组合中的按非递减或非递增顺序排列,以避免重复的组合。例如,每次递归调用时,只允许选择大于等于当前,这样组合将按非递减顺序排列,避免重复。 但用户的问题可能不需要总和等于某个值,而是生成所有可能的子集,或者排列。需要更明确的问题定义。 总结来说,用户需要的是用C++递归解决数字组合问题的方案,可能涉及组合枚举、排列生成、整分解等。根据提供的引用,用户提供的引用[1]是组合枚举,引用[4]是整分解的方案计算,但用户需要生成具体的组合。 因此,结合这些信息,我将提供一个递归解决方案,用于生成所有可能的数字组合,满足特定条件,例如和为n,或者选择m个允许重复等,并解释其工作原理,同时参考用户提供的引用中的代码结构。</think>以下是使用C++实现数字组合问题的递归解决方案,以**生成所有允许重复元素组合**为例: ### 问题定义 给定数字范围$1$到$n$,从中选择$m$个(允许重复选择一个数字),生成所有可能的组合。例如,当$n=3$且$m=2$时,组合为: $$(1,1),\ (1,2),\ (1,3),\ (2,2),\ (2,3),\ (3,3)$$ ### 递归实现代码 ```cpp #include <iostream> #include <vector> using namespace std; int n, m; vector<int> combination; void dfs(int start, int depth) { if (depth == m) { // 达到组合长度要求 for (int num : combination) { cout << num << " "; } cout << endl; return; } for (int i = start; i <= n; ++i) { combination.push_back(i); // 选择当前数字 dfs(i, depth + 1); // 允许重复,下一层从i开始 combination.pop_back(); // 回溯 } } int main() { cin >> n >> m; dfs(1, 0); return 0; } ``` ### 关键逻辑说明 1. **递归终止条件**:当组合长度达到$m$时输出结果。 2. **循环控制**:`for (int i = start; i <= n; ++i)`确保组合按非递减顺序排列,避免重复(如防止同时出现$(1,2)$和$(2,1)$)。 3. **允许重复**:通过传递`i`而非`i+1`到下一层递归,允许重复选择当前数字[^1]。 4. **回溯操作**:通过`push_back`和`pop_back`维护组合状态。 --- ### 变体问题:不允许重复的组合 若需生成**不重复**的组合(如从$n$个数字中选$m$个),只需修改递归调用为`dfs(i+1, depth+1)`,确保下一层从下一个数字开始选择: ```cpp void dfs(int start, int depth) { // ... 其他代码相同 dfs(i+1, depth + 1); // 禁止重复 } ``` --- ### 性能优化建议 1. **剪枝**:若组合过程中已无法满足最终条件(如剩余数字不足以填满组合),提前终止递归。 2. **记忆化**:若问题涉及大量重复计算(如组合求和),可通过缓存中间结果优化效率[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值