我们经常会遇到如下问题:给定一列数组或数,然后举例出所有可能的组合结果,这种问题我们直观的想法可能是:逐个遍历元素,求所有可能结果,这样当所给数量级较大时,可能会爆炸;此时我们通常的解法是将所有可能结果构造成一棵树,树中每一层都是一种可能的结果。(例如下图举例构造的树)
这篇文章,我们使用Java来实现递归+回溯构造树,在构造树之前我们需要定义数据结构来存储所有可能的元素,我经常会采用的是双向队列Deque<>和列表List<List<>>,其中Deque通常用来存储当前某条路径满足条件的解,而List用来存储所有可能的解。
定义代码如下:
//用来满足条件的结果
List<List<Integer>> list = new ArrayList<>();
//定义双向队列
Deque<Integer> que = new ArrayDeque<>();
而我们通常采用向Deque队尾当中加元素实现递归,删除元素实现回溯;
递归、回溯代码如下:(通常在一个for循环体当中)
que.addLast(i);
//递归
dfs(n,k,i+1,que,list);
//回溯
que.removeLast();
有了以上的简单概述,可能对此类问题还没有较深的印象,那么接下来举例两道通过构造递归树来求解的问题:
题1:Leetcode77.组合
题目概述:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
算法思路:
我们可以一次遍历,比如说第一个数取1、取2…取n的时候,然后在第一个数取1情况下第二个数取2、第一个数取2情况下第二个数取3…(这里为了实现所有递归的可能不重复,我们在for循环初始值设置的时候往往是前一个数+1,这样就能避免重复);通过这种方法构造递归树,然后当Deque当中元素个数等于k的时候(递归边界)结束递归,然后进行回溯(向树的上一层退一格然后再往没有走过的枝杈遍历),递归所有可能的结果。
算法实现(Java版):
class Solution {
public List<List<Integer>> combine(int n, int k) {
/**本题还是和上一次的题目类似使用递归+回溯方法 */
//用来满足条件的结果
List<List<Integer>> list = new ArrayList<>();
//定义双向队列
Deque<Integer> que = new ArrayDeque<>();
dfs(n,k,1,que,list);
return list;
}
public void dfs(int n,int k,int begin,Deque<Integer> que,List<List<Integer>> list){
if(que.size()==k){
//达到递归条件
list.add(new ArrayList<>(que));
return;
}
for(int i=begin;i<=n;i++){
//这里从begin开始就是为了避免重复元素
que.addLast(i);
//递归
dfs(n,k,i+1,que,list);
//回溯寻找下一种可能
que.removeLast();
}
}
}
题2:Leetcode78.子集
题目概述:
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
算法思路:
这题,我们通过读题一看,同样也是列举所有可能的结果,这样的问题常常通过构造递归树来实现;和上一题不同的是,这题的结果长度可能为0、1…nums.length;那么我们只要通过设置递归结束的条件为Deque的长度等于子集中元素的个数即可。
ps:
在这里我们一定要注意,在for循环中实现递归的时候,for循环的结束条件是为了找到所有可能的解,这里必须将nums数组全部遍历到。
算法实现:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
Deque<Integer> que = new ArrayDeque<>();
//实现子集的求解
for(int i=0;i<=nums.length;i++){
dfs(nums,i,0,que,list);
}
return list;
}
public void dfs(int[] nums,int len,int begin,Deque<Integer> que,List<List<Integer>> list){
if(que.size()==len){
list.add(new ArrayList<>(que));
return;
}
//将所有可能的解都遍历到
for(int i=begin;i<nums.length;i++){
que.addLast(nums[i]);
dfs(nums,len,i+1,que,list);
que.removeLast();
}
}
}