组合总和及结果显示_20230827

组合总和-兼论回溯方法_20230827

  1. 前言

针对组合总和的不同方法讨论,本文引出两类常见的回溯结构模式,回溯方法是众所周知的知识,它是在计算过程中,对相邻前后状态的记忆及回退,引导程序进入历史某一状态,重新开始基本的计算。

  1. 问题描述

组合总和问题源于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. 程序实现

以示例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

  1. 小结

本问题针对组合总和问题解决过程中,阐述了回溯的两种不同形式,两类形式都可以取得相同效果,读者可以根据自身需要,选择其中之一作为解题方法。本博文仅仅针对解题新得进行记录。

参考资料:

https://leetcode.cn/problems/combination-sum/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值