组合总和-兼论回溯方法_20230827
- 前言
针对组合总和的不同方法讨论,本文引出两类常见的回溯结构模式,回溯方法是众所周知的知识,它是在计算过程中,对相邻前后状态的记忆及回退,引导程序进入历史某一状态,重新开始基本的计算。
- 问题描述
组合总和问题源于Leetcode习题练习,题目描述为:
给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为
target
的不同组合数少于150
个。
本题目为典型求解集合问题,它不同于常规求解集合问题的地方在于,集合定义为无序状态,声明下面集合为同质集合,剔除同质集合或避免程序求出同质集合为程序需要解决的首要任务。题干中强调的,你可以按任意顺序返回这些组合,强调的也是需要满足这一规则。
<a,b,c>, <b,a,c>,<c,a,b>,<a,c,b><b,c,a><c,b,a>
具体看2个实际例子,
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例1当中[2,2,3],[3,2,2]及[2,3,2]视为同一解,在最终解结论中,需要罗列任一个即可。示例2当中[2,3,3],[3,2,3]及[3,3,2]视为同一解。
- 程序实现
以示例1进行过程描述,要实现目标值,可以选取或放弃集合中某元素,为了避免重复的集合重新,只能选取当前位置及后续位置的元素,禁止选取当前元素前的任何元素。这就需要施加条件对操作对象进行有效筛选,确保仅选取当前及后续的元素,禁止每次选择所有对象。
首先定义解结构,输出不同满足要求的组合,需要定义解的数量及每个解当中所含有的元素数量,为了程序方便,定义如下解结构。
声明的全局变量temp数组跟踪回溯过程中的元素, temp_size记录当前temp数组中所包含元素数量,也可以理解为当前数组的下一元素下标值。
ans数组中储存指针,它属于指针数组,对于每个有效的解,它都储存在某个一维数组中,那么一维数组的指针就储存在ans数组当中。ans_len记录当前解数量,elem_len数组记录每个解数量中包含的对象数目。
#define N 1001
int temp[N];
int temp_size;
/**
* @brief State the structure of solution
*
*/
typedef struct answer
{
int *ans[N];
int ans_len;
int elem_len[N];
}answer, *answer_ptr;
定义头文件combination_num.h
/**
* @file combination_num.h
* @author your name (you@domain.com)
* @brief
* https://leetcode.cn/problems/combination-sum/
* @version 0.1
* @date 2023-08-27
*
* @copyright Copyright (c) 2023
*
*/
#ifndef COMBINATION_SUM_H
#define COMBINATION_SUM_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 1001
int temp[N];
int temp_size;
/**
* @brief It is a dynamic data structure
*
*/
typedef struct answer
{
int *ans[N];
int ans_len;
int elem_len[N];
}answer, *answer_ptr;
void combination_sum(int *nums, int n, int index, int sub_num,int target, answer_ptr res);
void display_result(answer_ptr res);
#endif
我们加下来一一讨论回溯的两种实现方式以及实现的具体过程,第一种实现方式不采用循环语句,需要利用两个相邻的递归函数实现。
3-1. 无循环方式
temp[temp_size++]=nums[index];
combination_sum(nums,n,index,sub_num+nums[index],target,res);
temp_size--;
combination_sum(nums, n, index+1, sub_num, target, res);
combination_sum(nums,n,index,sub_num+nums[index],target,res)对下标为index的对象利用DFS方式递加,过程中判断是否等于target或大于target; temp_size–表示记录数组回退,求和中将剔除nums[index]元素; index+1表示跳过当前元素,仅计算对象的后续元素。
3-2. 循环方式
for(i=index;i<n;i++)
{
temp[temp_size++]=nums[i];
combination_sum(nums, n, i, sub_num + nums[i], target, res);
temp_size--;
}
循环方式中采用变量i来跟踪元素位置变化,规定元素起始位置为index,只有在index及之后的元素才能进入操作数范围,确保集合的唯一性。
无循环方式的实现程序,
/**
* @file combination_num.c
* @author your name (you@domain.com)
* @brief
*
* @date 2023-08-27
*
* @copyright Copyright (c) 2023
*
*/
#ifndef COMBINATION_SUM_C
#define COMBINATION_SUM_C
#include "combination_sum.h"
void combination_sum(int *nums, int n, int index, int sub_num, int target, answer_ptr res)
{
if (sub_num > target)
{
return;
}
if (sub_num == target)
{
int *res_ptr;
res_ptr=(int*)malloc(sizeof(int)*temp_size);
memcpy(res_ptr, temp, sizeof(int) * temp_size);
res->ans[res->ans_len]=res_ptr;
res->elem_len[res->ans_len]=temp_size;
res->ans_len+=1;
return;
}
if(index==n)
{
return;
}
temp[temp_size++]=nums[index];
combination_sum(nums,n,index,sub_num+nums[index],target,res);
temp_size--;
combination_sum(nums, n, index+1, sub_num, target, res);
}
void display_result(answer_ptr res)
{
int i;
int j;
for(i=0;i<res->ans_len;i++)
{
printf("{ ");
for(j=0;j<res->elem_len[i];j++)
{
printf("%d ",res->ans[i][j]);
}
printf("},");
}
}
#endif
循环方式的实现程序,
/**
* @file combination_num.c
* @author your name (you@domain.com)
* @brief
*
* @date 2023-08-27
*
* @copyright Copyright (c) 2023
*
*/
#ifndef COMBINATION_SUM_C
#define COMBINATION_SUM_C
#include "combination_sum.h"
void combination_sum(int *nums, int n, int index, int sub_num, int target, answer_ptr res)
{
int i;
if (sub_num > target)
{
return;
}
if (sub_num == target)
{
int *res_ptr;
res_ptr=(int*)malloc(sizeof(int)*temp_size);
memcpy(res_ptr, temp, sizeof(int) * temp_size);
res->ans[res->ans_len]=res_ptr;
res->elem_len[res->ans_len]=temp_size;
res->ans_len+=1;
return;
}
if(index>=n)
{
return;
}
for(i=index;i<n;i++)
{
temp[temp_size++]=nums[i];
combination_sum(nums, n, i, sub_num + nums[i], target, res);
temp_size--;
}
}
void display_result(answer_ptr res)
{
int i;
int j;
for(i=0;i<res->ans_len;i++)
{
printf("{ ");
for(j=0;j<res->elem_len[i];j++)
{
printf("%d ",res->ans[i][j]);
}
printf("},");
}
}
#endif
- 小结
本问题针对组合总和问题解决过程中,阐述了回溯的两种不同形式,两类形式都可以取得相同效果,读者可以根据自身需要,选择其中之一作为解题方法。本博文仅仅针对解题新得进行记录。
参考资料:
https://leetcode.cn/problems/combination-sum/