回溯算法1

二叉树篇中已经有过一些关于回溯算法的记录,当时说到回溯算法必须要和递归算法成套使用。要用到回溯算法的问题,通常是需要全局搜索的(暴力解法),因此回溯算法在可选的情况下并不是最优解(慎用)。

对于回溯法的理解,我这里引用 如何理解回溯法, https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw作者对回溯的理解。回溯可以抽象为在一个集合中递归查找子集,随着递归进行,待查集一定是逐渐缩小的。由于是遍历,回溯算法的返回值通常是void。参数:除了要有输入集合,输出集合外,还要自主设计参数来缩小待查子集,设置递归终止参数。用一个组合问题来说明回溯算法的写法。

组合问题1:对于给定的集合set={1,2, ... , n},输出所有的k个数组成的子集。

这其实是一个组合问题,所有可能的组合数目为:

\binom{n}{k}

这一点很清楚,现在需要输出这样的集合。使用回溯法,设计参数时,nums是传入的集合,k是控制递归终点的参数,随着递归深入,k逐渐减小。此外,用一个index来控制选取元素的范围,这是为了避免重复。


vector<int> path;
vector<vector<int>> res;
void backTrack(vector<int> nums, int k, int index){
//搜索k个数,则记录这个path
if(k==0){res.push_back(path);return ;}

for(int i=index;i<nums.size();++i){
path.push_back(nums[i]);
backTrack(nums, k-1, i+1);
path.pop_back();

}

}

当然,这个问题还可以再剪剪枝,当待选集合数已经少于需要的元素数,肯定是无法组成一个path了,因此循环到此处就可以直接返回了。

vector<int> path;
vector<vector<int>> res;
void backTrack(vector<int> nums, int k, int index){
//搜索k个数,则记录这个path
if(k==0){res.push_back(path);return ;}

for(int i=index;i<nums.size();++i){
if(k+index>nums.size()){return ;}
path.push_back(nums[i]);
backTrack(nums, k-1, i+1);
path.pop_back();

}

}

进一步,在给定的集合中找出所有的和为targetSum的k个数的组合。这个问题跟二叉树中搜索和为Sum的路径类似。在那个问题中也使用了回溯算法。基于上面的代码,增加一个targetSum的参数。

vector<int> path;
vector<vector<int>> res;
void backTrack(int targetSum, vector<int> nums, int k, int index){
//搜索k个数,则记录这个path
if(k==0 && targetSum==0){res.push_back(path);return ;}
if(k==0){return ;}

for(int i=index;i<nums.size();++i){
if(k+i>nums.size() || targetSum<0){return; }
path.push_back(nums[i]);
targetSum-=nums[i];
backTrack(targetSum, nums, k-1, i+1);
path.pop_back();
targetSum+=nums[i];
}

}

组合问题2.

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。注意:candidates 中的数字可以无限制重复被选取。

这个问题中有一个重要的地方,集合中的数字可以被无限制使用。这时终止条件就只剩下target了,对元素个数k,重复性都不再做限制。那么,startindex这个位置参数要如何处理呢?如果要确保res中的组合不重复,递归就不能往前面取元素,而只能从当前这个位置取。

vector<int> path;
vector<vector<int>> res;
void backTrack(int targetSum, vector<int> nums, int startindex){
//满足target,则记录这个path
if(targetSum==0){res.push_back(path);return ;}
if(targetSum<0){return ;}//target<0,递归也要终止
//需要考虑一个问题:去重.就是需要去除重复的组合,但是单个组合中的元素又是可以选取的
for(int i=startindex;i<nums.size();++i){
path.push_back(nums[i]);
targetSum-=nums[i];
backTrack(targetSum, nums, i);//这里递归的startindex不用次次递增,因为可以选取当前这个元素
path.pop_back();
targetSum+=nums[i];
}

}

组合问题3

给定一个无重复元素的数组 nums和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。注意:nums中的数字在每个组合中只能使用一次,输出的结果中无重复。

这个与第一个问题的区别在于,数组中存在重复元素,则每一个组合中肯定要有相同的元素。但是输出结果中又不能有重复组合,且不限制组合中元素个数。例如:nums={1,2,2,2,5}, target=5;输出应当是:res={ {1,2,2}, {5}},重复的{1,2,2}组合不能出现在res结果中。

vector<int> path;
vector<vector<int>> res;
int* used= new int(nums.size(), 0);
void BackTrack(vector<int> nums, int target, int startIndex, int &used){
//终止条件,仅有一个target==0 是符合的
if(target==0) {res.push_back(path); return ;}
if(target < 0) { return ;}

for(int i=startIndex; i<nums.size();++i){
//去重,也就是如果前一个元素nums[i-1]==nums[i],且used[i-1]==0,则说明nums[i]已经是重复的元素
if(nums[i-1]==nums[i] && used[i-1]==0) continue;

target-=nums[i];
path.push_back(nums[i]);
used[i]=1;
BackTrack(nums, target, i+1, used);
target+=nums[i];
path.pop_back();
used[i]=0;
}

}

最后用框图来表示整个流程:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值