zlatan的算法笔记2--回溯法解决组合总和问题(力扣39题)

算法笔记

本题也是组合问题的一种,之前做过的组合问题是给定几个数字n让我们找到k个数的组合,这次则是给定了数组与目标值,考虑应该怎么去求解。本题仍然是跟随代码随想录进行学习,视频链接为组合总和

题目描述

在这里插入图片描述
理解一下题目,题目中给定了一个不含有重复元素的数组candidates以及目标值target,要求让我们找到数组当中的和为target的所有组合,并且每个组合当中的原数组里的数字允许被重复选取。

解题过程

之前我们谈到了回溯的模板以及解题的三个步骤,下图先放出模板用于回忆。

void backtracking(参数) {
	if (结束条件) {
	存结果并且返回;
	}
	for(横向遍历) {
	处理节点;
	backtracking(递归);
	关键步骤回溯;
	}
}

其次我们构建树,下图为我们构造的树,图里面打叉的与涂黑的部分都是我们不需要的,我们要的是最终加和为目标值的总和,所以三部曲就可以开始了。
在这里插入图片描述

1.选择参数:与之前一样需要一个记录每条路径的一维数组path以及返回最后结果的二维数组result,然后我们要多考虑的部分就是加和,所以我们要额外定义一个sum来表示总和,我们代码中将这些都设置为全局变量,另外我们的回溯函数中也需要startIndex去记录节点,与上题类似。

2.终止条件:看图就很好理解,如果我们的sum大于target就说明这个组合没什么用了,直接return,如果sum==target就让result数组去记录这个组合,同时也要return。

3.循环递归回溯:这部分内容也是与之前类似,给定一个for循环,先用path数组收集我们的每一个点,使用sum加上每一个点来与target进行判断,随后回溯,记住题目要求数组可以重复使用所以我们不用同之前一样去在参数中对i+1,直接i就可以了,回溯完撤销节点,与模板中的过程时是差不多的,这里要记得sum还要将之前加的部分退出去,不然撤销的节点就会发生变化,至此任务就完成了。

代码部分

class Solution {
public:

    vector<int> path;
    vector<vector<int>> result;
    int sum;

    void backtracking(vector<int>& candidates, int target, int startIndex) {
        if(sum > target) {
            return;
        }
        if(sum == target) {
            result.push_back(path);
            return;
        }

        for(int i = startIndex; i < candidates.size(); i++) {
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtracking(candidates, target, i);
            sum -= candidates[i];
            path.pop_back();
        }

    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0);
        return result;
    }
};

剪枝优化

给定的数组如果是有序的数组我们可以发现,每次选取元素的时候,我们左边得到的组合【这里认为顺序从小到大】往往会更深,而右边较大的部分则会提前满足target或者大于target,所以先对整个数组进行排序就可以更清晰地梳理我们的树,之后我们去考虑for循环中i的条件,1.2中学习过剪枝基本就是对于for循环的条件进行处理。在终止条件中,如果sum大于target就跳出循环,但是在for循环中递归仍然进行,同时sum是每一层candidates的加和,所以可以判断 sum + candidates[i] <= targe就是我们所需要的条件,至此操作完成,代码如下:

class Solution {
public:

    vector<int> path;
    vector<vector<int>> result;
    int sum;

    void backtracking(vector<int>& candidates, int target, int startIndex) {
        if(sum > target) {
            return;
        }
        if(sum == target) {
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++){
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtracking(candidates, target, i);
            sum -= candidates[i];
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0);
        return result;
    }
};

结语

我个人认为本题是比较容易理解的,大致的代码逻辑也与上一题比较相似,但是在剪枝这一部分当中的思考就会比较灵活,我只片面的想到了在for循环的条件中判断一层中如果sum值已经大于target就没有必要再进行下去,未考虑到排序,所以仍需要不断积累经验去完善整部分代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值