递归产生左分支,循环产生右分支,
我们在dfs中传入的参数有:题目给定的参数,depth,path,ans
depth表示第几次循环,path表示两个节点的路径,ans表示符合条件的路径集合
求子集参数:数组,path,ans
全排列:数组,path,ans
组合:n个数任意k个组合,n,k,path,ans
组合总和:数组,目标值,depth,path,ans
递归的层数可以用path.size()表示
全排列:123和132不一样,对于i=2,num[2]=3,path=1,2,path.size()❤️,
继续新一层递归,这个时候需要记录nums[2],因此新一轮递归要从0开始。
组合子集新一轮递归从i+1开始
组合总和新一轮递归从i开始,因为可以每一个数可以多次重复使用。
1.子集
//求子集
import java.util.ArrayList;
import java.util.List;
//求一个数组的子集,需要传入一个数组和一个记录递归深度的变量。
public class Solution {
public List<List<Integer>>subSets(int[]nums){
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer>path=new ArrayList<>();
dfs(0,nums,path,ans);
return ans;
}
public void dfs(int depth,int[]nums,List<Integer>path,List<List<Integer>> ans){
//记录每一条路径
// System.out.println("depth:"+depth);
ans.add(new ArrayList<>(path));
for (int i=depth;i<nums.length;i++){
path.add(nums[i]);
dfs(i+1,nums,path,ans);
path.remove(path.size()-1);
}
}
public static void main(String[] args) {
int[] nums={1,2,3};
Solution solution=new Solution();
List<List<Integer>> list = solution.subSets(nums);
System.out.println(list);
}
}
2.全排列
//全排列
import java.util.ArrayList;
import java.util.List;
public class Solution1 {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>>ans=new ArrayList<>();
List<Integer>path=new ArrayList<>();
// dfs(0,nums,path,ans);
dfs(nums,path,ans);
return ans;
}
// private void dfs(int depth,int[]nums,List<Integer>path,List<List<Integer>>ans)
private void dfs(int[]nums,List<Integer>path,List<List<Integer>>ans){
//记录根节点到叶节点的路径
// System.out.println(depth);
// if (depth==nums.length){
// ans.add(new ArrayList<>(path));
// return;
// }
if (path.size()==nums.length){
ans.add(new ArrayList<>(path));
return;
}
for (int i=0;i<nums.length;i++){
if (path.contains(nums[i])){
continue;
}
path.add(nums[i]);
dfs(nums,path,ans);
path.remove(path.size()-1);
}
}
public static void main(String[] args) {
int[] nums = {1, 2, 3};
Solution1 solution = new Solution1();
List<List<Integer>> lists = solution.permute(nums);
System.out.println(lists);
}
}
组合
//组合
import java.util.ArrayList;
import java.util.List;
public class Solution2 {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>>ans=new ArrayList<>();
List<Integer>path=new ArrayList<>();
dfs(n,k,1,path,ans);
return ans;
}
//path.size()=depth
private void dfs(int n, int k,int depth, List<Integer>path, List<List<Integer>> ans) {
// 递归终止条件是:path 的长度等于 k
// System.out.println(path.size()==depth);
// System.out.println("---------------------");
// 记录从根节点到页节点长度为2的路径
if (path.size() == k) {
ans.add(new ArrayList<>(path));
return;
}
// System.out.println("depth:"+depth);
// if (depth == k) {
// ans.add(new ArrayList<>(path));
// return;
// }
// 遍历可能的搜索起点
for (int i = depth; i <=n; i++) {
// 向路径变量里添加一个数
path.add(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k,i+1,path, ans);
// 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.remove(path.size()-1);
}
}
public static void main(String[] args) {
Solution2 solution=new Solution2();
List<List<Integer>> combine = solution.combine(5, 2);
System.out.println(combine);
}
}
组合总和
//组合总和
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution3 {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer>path=new ArrayList<>();
if (candidates.length == 0) {
return ans;
}
// 对数组排好序,如果
Arrays.sort(candidates);
dfs(candidates,target,0,path,ans);
return ans;
}
private void dfs(int[] candidates, int target, int depth, List<Integer>path, List<List<Integer>> ans) {
//记录从根节点到叶节点权值之和为target的路径
if (target == 0) {
ans.add(new ArrayList<>(path));
return;
}
/*递归产生左分支,循环产生右分支,如果target - candidates[i] < 0,由于已经排好序,target - candidates[i++] 必然小于0
* 因此可以使用当target - candidates[i] < 0,直接可以终止循环,也就是不再产生右分支,因为即使产生右分支,target - candidates[i++] 必然小于0
* 这就是剪枝。
* */
for (int i = depth; i < candidates.length; i++) {
if (target - candidates[i] < 0) {
break;
}
path.add(candidates[i]);
dfs(candidates,target - candidates[i],i, path, ans);
path.remove(path.size()-1);
}
}
public static void main(String[] args) {
Solution3 solution = new Solution3();
int[] candidates = new int[]{2, 3, 6, 7};
int target = 7;
List<List<Integer>> res = solution.combinationSum(candidates, target);
System.out.println("输出 => " + res);
}
}
电话号码的字母组合
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Solution {
public List<String> letterCombinations(String digits) {
List<String>ans=new ArrayList<>();
if (digits.length() == 0) {
return ans;
}
Map<Character, String> phoneMap = new HashMap<Character, String>() {{
put('2', "abc");
put('3', "def");
put('4', "ghi");
put('5', "jkl");
put('6', "mno");
put('7', "pqrs");
put('8', "tuv");
put('9', "wxyz");
}};
StringBuffer stringBuffer=new StringBuffer();
dsf(digits,phoneMap,stringBuffer,ans);
return ans;
}
public void dsf(String digits, Map<Character, String> phoneMap,StringBuffer stringBuffer,List<String>ans){
if (stringBuffer.length()==digits.length()){
ans.add(stringBuffer.toString());
return;
}
char digit=digits.charAt(stringBuffer.length());
String letters=phoneMap.get(digit);
int letterCount=letters.length();
for (int i=0;i<letterCount;i++){
stringBuffer.append(letters.charAt(i));
dsf(digits,phoneMap,stringBuffer,ans);
stringBuffer.deleteCharAt(stringBuffer.length()-1);
}
}
public static void main(String[] args) {
String str="234";
Solution solution=new Solution();
System.out.println(solution.letterCombinations(str));
}
}
括号生成
class Solution {
public List<String> generateParenthesis(int n) {
List<String>ans=new ArrayList<>();
StringBuilder track=new StringBuilder();
dfs(n,n,track,ans);
return ans;
}
public void dfs(int left,int right,StringBuilder track,List<String>ans){
if(right<left) return;
if(left<0 || right<0) return;
if(left==0 && right==0){
ans.add(track.toString());
return;
}
//注意该题相当于二叉树,因为没有使用循环,上面的题使用了循环,相当于一个多叉树。
//生成左括号
track.append('(');
dfs(left-1,right,track,ans);
track.deleteCharAt(track.length()-1);
//生成右括号
track.append(')');
dfs(left,right-1,track,ans);
track.deleteCharAt(track.length()-1);
}
}
该题可以和二叉树的遍历方式进行比较,代码如下
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
dfs(res,root);
return res;
}
void dfs(List<Integer> res, TreeNode root) {
if(root==null) {
return;
}
//按照 左-打印-右的方式遍历
dfs(res,root.left);
res.add(root.val);
dfs(res,root.right);
}
}