Find all possible combinations of k numbers that add up to a number n, given that only numbers from 1 to 9 can be used and each combination should be a unique set of numbers.
Example 1:
Input: k = 3, n = 7
Output:
[[1,2,4]]
Example 2:
Input: k = 3, n = 9
Output:
[[1,2,6], [1,3,5], [2,3,4]]
该题采用了标准的回溯法BackTracking。之前的22. Generate Parentheses 也是采用的该法,在这里稍微总结下回溯法的思想。
对于一个多解问题,则可以构建出其的解空间树,该问题的解就是树的某些节点。因此对于这颗解空间树,我们可以采用BFS和DFS的搜索思想。回溯法就是基于深度优先搜索DFS进行的。由于是多解问题,那么肯定是多变量的,对于每个参量的选择就是一次搜索(迭代/循环),当每个参量都选好具体值后就确定了某一个解;在确定各个参量的具体数值时,我们采用剪枝函数(即带条件运行的函数)去进行迭代,以排除掉一些不符合要求的解,加快求解速度。
回溯法可用于求解单个符合要求的解,也可以用于求解全部符合要求的解。不过值得注意的是,虽然有利用剪枝函数,不过回溯法在求解全部解时需要遍历整个解空间树,因此会造成复杂度呈指数型上升,所以属于暴力搜索的一种。
根据五大常用算法之四:回溯法,算法的结构如下:
循环版本:
int a[n],i;
初始化数组a[];
i = 1;
while (i>0(有路可走) and (未达到目标)) // 还未回溯到头
{
if(i > n) // 搜索到叶结点
{
搜索到一个解,输出;
}
else // 处理第i个元素
{
a[i]第一个可能的值;
while(a[i]在不满足约束条件且在搜索空间内)
{
a[i]下一个可能的值;
}
if(a[i]在搜索空间内)
{
标识占用的资源;
i = i+1; // 扩展下一个结点
}
else
{
清理所占的状态空间; // 回溯
i = i –1;
}
}
递归版本:
int a[n];
try(int i)
{
可以写入剪枝函数判别式加快迭代;
if(i>n)
输出结果;
else
{
for(j = 下界; j <= 上界; j=j+1) // 枚举i所有可能的路径
{
if(fun(j)) // 满足限界函数和约束条件
{
a[i] = j; //将解中变量i标记为值j并逐步进入迭代
... // 其他操作
try(i+1); //进入下一轮/下一变量选择
回溯前的清理工作(如a[i]置空值等);
}
}
}
}
本题代码:
class Solution {
public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> comb = new ArrayList<>();
helper(ans, comb, k, 1, n);
return ans;
}
public void helper(List<List<Integer>> ans, List<Integer> comb, int k, int i_th, int rest){
if(comb.size() > k || rest < 0)//提前回溯条件/剪枝条件
return;
if(comb.size() == k && rest == 0){//找到解(对应a,b,c...的确切值)的条件
List<Integer> temp = new ArrayList<>(comb);
ans.add(temp);
return;
}
for(int i = i_th; i <= 9; i++){//同一变量选取不同值时进入不同的下一轮迭代
comb.add(i);//记录当前变量a的选择值并准备进入变量b的选择迭代
helper(ans, comb, k, i+1, rest-i);
comb.remove(comb.size()-1);//恢复操作,保证当前变量a的值1选取不对值2选取造成影响。
}
}
}