前言
由于之前的一道回溯算法题目没做出来,最近一段时间专门练习回溯算法的题目,程序写的多了就有感觉知道哪里有问题,算法练习还是集中的专项练习为好。
组合总和
题目一描述
给定一个不重复元素的数组和一个目标值target,找出数组中能够组合成目标值的子集合,注意数组元素可以重复使用。
例如:candidates = [2,3,6,7] target = 7
返回值:
[
[7],
[2,2,3]
]
题目一分析
构建一颗树来表示,以target为根节点以数组元素为边做减法操作,叶子节点为0的一条路径为有效路径,去掉有效路径中重复的路径,对于叶子节点为负数的节点表示无效的路径。
代码如下
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
if (candidates.length == 0)
return res;
dfs(candidates, 0, target, res, temp);
return res;
}
//深度优先,传入对应的数组,当前开始的位置,目标值,以及结果容器和当前路径
public void dfs(int[] candidates, int start, int target, List<List<Integer>> res, List<Integer> path) {
//小于0表示不可到达的路径
if (target < 0)
return;
//等于0表示有效路径
if (target == 0) {
res.add(new ArrayList(path));
return;
}
//回溯算法核心
for (int i = start; i < candidates.length; i++) {
path.add(candidates[i]);
//当前传递的i表示可以重复的使用改值
dfs(candidates, i, target - candidates[i], res, path);
//剪枝操作,回溯到前一个位置
path.remove(path.size() - 1);
}
}
求指定长度的子集
题目二描述
给定两个整数n和k求,返回1…n中所有可能k个数的组合。
输入 n=4,k=2
返回:
[
[1,2],
[1,3],
[1,4],
[2,3],
[2,4],
[3,4]
]
题目二分析
每次从数组中选择一个数加入路径中,下一个元素只能够从后面的那些元素中选择(需要变更i的位置,与上面的不同,上面的元素可以重复),选中k个元素的时候停止。如下面的图所示:
代码如下
public static List<List<Integer>> getCombine(int n, int k) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
dfsNum(res, temp, k, n, 1);
return res;
}
public static void dfsNum(List<List<Integer>> res, List<Integer> path, int k, int n, int start) {
//递归回溯的边界,当前的路径大于k个元素,返回
if (path.size() > k)
return;
//当前的元素个数等于k,添加到总的容器中
if (path.size() == k) {
res.add(new ArrayList(path));
return;
}
//回溯核心,这个元素只会出现1次(不可能出现k个1或者k个2这样的组合)
for (int i = start; i <= n; i++) {
path.add(i);
dfsNum(res, path, k, n, i + 1);
path.remove(path.size() - 1);
}
}
求子集合
题目三描述
给定一个数组,要求给出数组的所有子集。该题目是从同学博客中看到的,他说自己面试了阿里,好吧,我就相信了,其实有点酸,我没有收到阿里爸爸的面试邀请,好气呦。
题目的博客
题目三分析
其实和上一个题目比较类似,就是构建一棵树,进行选择,只是这次是找到所有的子集,不是一个定长的集合了,上面相当于多一次的筛选。图我就不画了,我也想要阿里的面试。
代码如下
public static List<List<Integer>> getSub(int[] num) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> temp = new ArrayList<Integer>();
dfsSub(res, temp, num, 0);
return res;
}
//回溯算法
public static void dfsSub(List<List<Integer>> res, List<Integer> path, int[] num, int start) {
//当前的元素已经越界了,返回
if (start > num.length)
return;
//只要不是空路径就加入总的集合中
if (path.size() > 0)
res.add(new ArrayList<>(path));
//回溯的核心,多理解几次,多写几次就好了
for (int i = start; i < num.length; i++) {
path.add(num[i]);
dfsSub(res, path, num, i + 1);
path.remove(path.size() - 1);
}
}