DFS深度优先遍历例题总结

递归产生左分支,循环产生右分支,
我们在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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值