39. 组合总和 + 递归 + 回溯 + 存储路径
39. 组合总和
LeetCode_39
题目描述
题解分析
- 这是一道面试常考题:https://www.nowcoder.com/discuss/582025?source_id=discuss_experience_nctrack&channel=-1
- 题目考察的是递归搜索,这里使用的方法是回溯法。
- 这题需要画出递归树来比较直观,递归的出口就是目标值减为0或者使用的数的序号超出了候选数组的大小。
- 递归的函数体中需要根据是否选择当前数来递归,在不选择当前数时,index(搜索序号)需要加1;而选择当前数时,因为候选数组中的数可以重复选择,所以index还是不变。
- 题目中需要注意的一个问题就是路径的存储,这里考虑到java的引用问题,将path存入result列表中时需要根据当前列表新建一个链表后add进result中。
java代码
package com.walegarrett.interview;
import java.util.ArrayList;
import java.util.List;
/**
* @Author WaleGarrett
* @Date 2021/2/27 17:53
*/
/**
* 题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
* candidates 中的数字可以无限制重复被选取。
*/
/**
* 解法:回溯法
*/
public class LeetCode_39 {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
List<Integer> list = new ArrayList<>();
dfs(candidates, target, 0, list, result);
return result;
}
public void dfs(int[] condidates, int target, int index, List<Integer> path, List<List<Integer>> result){
if(index == condidates.length)
return;
//找到一条路径
if(target == 0){
//注意:这里不能直接result.add(path),因为path是在回溯中会改变的,这样只存储了list的地址,地址是不变的。
result.add(new ArrayList<>(path));
return;
}
//跳过当前数
dfs(condidates, target, index+1, path, result);
//不跳过当前数
if(target - condidates[index] >= 0){
path.add(condidates[index]);
dfs(condidates, target-condidates[index], index, path, result);
path.remove(path.size()-1);
}
}
}
复杂度分析
- 时间复杂度:O(S),其中 S 为所有可行解的长度之和。从分析给出的搜索树我们可以看出时间复杂度取决于搜索树所有叶子节点的深度之和,即所有可行解的长度之和。在这题中,我们很难给出一个比较紧的上界,我们知道 \(O(n \times 2^n)\)是一个比较松的上界,即在这份代码中,n 个位置每次考虑选或者不选,如果符合条件,就加入答案的时间代价。但是实际运行的时候,因为不可能所有的解都满足条件,递归的时候我们还会用 target - candidates[idx] >= 0 进行剪枝,所以实际运行情况是远远小于这个上界的。
- 空间复杂度:\(O(\textit{target})\)。除答案数组外,空间复杂度取决于递归的栈深度,在最差情况下需要递归 \(O(\textit{target})\) 层。
Either Excellent or Rusty