一、问题(引用力扣39题)
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合
二、回溯
一种使用多层循环并且加入判断减少循环的方法;在力扣的本题的讨论种看到了一个比较通用的模板(这里使用php代码写一下)
function backto(路径,选择路径列表,目标){
if(满足条件) result[] = 路径;
for(选择列表){
做选择
backto(路径,选择列表,目标(目标可能会在循环的过程种产生变化));
撤销上面的选择,重新进入到for循环
}
}
直接上具体代码解释
class CK
{
public $res=[];
/**
* @param Integer[] $candidates
* @param Integer $target
* @return array
*/
function combinationSum($candidates, $target)
{
sort($candidates);
$len = count($candidates);
$this->back($candidates, $target, 0, $len, $temp_list = []);
return $this->res;
}
/**
* @param array $candidates 提供选择的列表
* @param integer $target 目标值
* @param integer $i 当前再操作选择列表的第几个数(从0开始)
* @param integer $len 选择列表的长度
* @param array $temp_list 零时保存我当前的选择组合(也就是上面写的模板的路线)
* @return mixed
*/
public function back($candidates, $target, $i, $len, $temp_list)
{
if ($target == 0) {
$this->res[] = $temp_list;
return;
}
for ($start = $i; $start < $len; $start++) {
if($target - $candidates[$start] < 0) return;
$temp_list[] = $candidates[$start];
// 从头开始再扫一遍
$this->back($candidates, $target - $candidates[$start], $start, $len, $temp_list);
array_pop($temp_list);
}
return;
}
}
在 combinationSum 函数中先对提供的选择列表进行了排序,这个对后面的影响不大可以忽略。直接看到back函数,函数一共五个参数,主要的三个参数 $candidates、 $target、 $temp_list,主要解释一下 $temp_list,是用来存储当前做的选择组合,可以看作是一个 篮子 ;
接下来看到
// 假如选择列表是 [A,B,C,D,E]
// 从提共的选择列表的第一个开始循环
for ($start = $i; $start < $len; $start++) {
// 判断每一个和目标值的大小,只有比目标值小的才可以继续一层循环选择,注意这个地方是下一层,并且目标值也是会变动的,并不是保证原有的,接下来的目标值=原始值-(当前选择组合种的每一个数)
if($target - $candidates[$start] < 0) return;
// 把选择列表中的一个数(A)加入到 ---篮子--- 中;
$temp_list[] = $candidates[$start];
// 从头开始再扫一遍,这里需要把目标值减去我们上面加入到篮子中的数字,进行接下来的选择,并把新篮子传递进去
$this->back($candidates, $target - $candidates[$start], $start, $len, $temp_list);
// 这里是把上面加入到篮子中的数(A)从篮子中拿出来,接下来会继续当前循环走到上面把B加入到篮子中,这就形成了回跳也就是回溯,
array_pop($temp_list);
}
然后是函数体中开始的判断
// 这个判断就是目标值变更成0,也就是篮子字物品的总值等与target初始值,那我们就把这个篮子中物品的组合存起来,回到进入当前函数的循环体中
if ($target == 0) {
$this->res[] = $temp_list;
return;
}
// ------------------------------------------------------------------
// 下面是伪代码解释:回到进入当前函数的循环体中
for ($start = $i; $start < $len; $start++) {
if($target - $candidates[$start] < 0) return;
$temp_list[] = $candidates[C];
// 假设target=C;并且当前页正好把C放进篮子
// $target = C-C = 0;说明篮子数值达到了饱和,会直接进入if判断并且return,那就不会进入函数体中的for,所有这里就只写了if,这个时候把篮子组合纪录保存下来后,就会进入下面array_pop(),
back([A,B,C,D], $target=C-C, $start, $len, [A,B,C]){
if ($target == 0) {
$this->res[] = $temp_list;
return;
}
}
// 这里会把 C 从篮子中取出,篮子中剩下 [A,B];之后会继续for循环,把D放入到篮子中去判断,
array_pop($temp_list);
}
这就是我简单的一点思路和理解,回溯的思想还需要不断的练习,主要是练习代码的实现方面。