DFS

简介: -

深度优先搜索是递归实现的,是要搜索整个二叉树的,在这个搜索的基础上,再加点回溯/剪枝的操作就是这一类排列组合的题了,是这个关系


  • 空间复杂度为O(h)O(h),对空间复杂度高的考虑DFS
  • 不具备最短性

回溯

现实无法回到过去,就在程序回到过去吧!

与动态规划的区别

共同点

  • 用于求解多阶段决策问题。多阶段决策问题即:

  • 求解一个问题分为很多步骤(阶段);
  • 每一个步骤(阶段)可以有多种选择。

不同点

  • 动态规划只需要求我们评估最优解是多少,最优解对应的具体解是什么并不要求。因此很适合应用于评估一个方案的效果;
  • 回溯算法可以搜索得到所有的方案(当然包括最优解),但是本质上它是一种遍历算法,时间复杂度很高。

优化

由于回溯算法的时间复杂度很高,因此在遍历的时候,如果能够提前知道这一条分支不能搜索到满意的结果,就可以提前结束,这一步操作称为 剪枝

排序主要的作用,让程序在深搜的过程中尽量排除掉不能搜索到结果的分支,以节约时间。在回溯搜索这种时间复杂度很大的算法中,先排序再剪枝有些时候是有必要的。


练习

力扣

子集

基本思路

注意!!!

变量 t 所指向的列表 在深度优先遍历的过程中只有一份 ,深度优先遍历完成以后,回到了根结点,成为空列表。

在 Java 中,参数传递是 值传递,对象类型变量在传参的过程中,复制的是变量的地址。这些地址被添加到 res 变量,但实际上指向的是同一块内存地址,因此我们会看到 66 个空的列表对象。

解决的方法很简单,在 ans.add(new ArrayList<Integer>(t)); 这里做一次拷贝即可。

class Solution {

   List<Integer> t = new ArrayList<Integer>();

   List<List<Integer>> ans = new ArrayList<List<Integer>>();


   public List<List<Integer>> subsets(int[] nums) {

       dfs(0, nums);

       return ans;

   }


   public void dfs(int cur, int[] nums) {

       if (cur == nums.length) {

           ans.add(new ArrayList<Integer>(t));

           return;

       }

       t.add(nums[cur]);

       dfs(cur + 1, nums);

       t.remove(t.size() - 1);

       dfs(cur + 1, nums);

   }

}


全排列(无重复数据)

题目

分析

可能在这里回想为什么没有出现单体,例如[1]或者[1,2]这种,这里的判断条件和子集那道题不一样,

子集那题的计数,可能在nums.length-1时执行了一次,所以出现只有一个数的数组,执行几次便有几个

class Solution {

   public List<List<Integer>> permute(int[] nums) {

       List<Integer> List = new ArrayList<>();

       List<List<Integer>> res = new ArrayList<>();

       boolean[] st = new boolean[nums.length];

       if(nums.length==0)return res;

       DFS(nums,res,List,st);

       return res;

   }

   public void DFS(int[] nums, List<List<Integer>> res, List<Integer> List, boolean[] st){

       if(List.size() == nums.length){

           res.add(new ArrayList<>(List));

           return;

       }

       for(int i = 0; i < nums.length; i ++){

           if(!st[i]){

               List.add(nums[i]);

               st[i]=true;

               DFS(nums,res,List,st);

               List.remove(List.size()-1);

               st[i]=false;

           }

       }

   }

}

基础不牢,多练练这个,注意代码规范

总结:

全排列2(有重复数据)

分析

拿到题目,发现重复元素,便想到重复必有影响,例如两个相同元素交换可能又是一组数据,想到了剪枝

因为全排列,所以数据不够的不会被选中,我们只需要让重复的数据无法继续即可

  • 如果这个数和前一个数相同且前一个数还没有用过,continue
  • 比如11'2可以,1'12不行,因为1'在1前面使用,1还未使用

class Solution {

   public List<List<Integer>> permuteUnique(int[] nums) {

       List<Integer> list = new ArrayList<Integer>();

       List<List<Integer>> res = new ArrayList<List<Integer>>();

       boolean[] st = new boolean[nums.length];

       Arrays.sort(nums);//要去重,故先排序

       DFS(nums,list,res,st);

       return res;

   }

   public void DFS(int[] nums, List<Integer> list, List<List<Integer>> res, boolean[] st){

       if(list.size()==nums.length){

           //注意不要写成List<Integer>

           res.add(new ArrayList<>(list));

           return;

       }

       for(int i = 0; i < nums.length; i ++){

           if(i>0&&nums[i]==nums[i-1]&&!st[i-1])continue;

           if(!st[i]){

               list.add(nums[i]);

               st[i]=true;

               DFS(nums,list,res,st);

               list.remove(list.size()-1);

               st[i]=false;

           }

       }

   }

}

组合总和

分析

还是全排列,然后将符合的加入,能优化就是剪枝,关键点排序,主要的作用,让程序在深搜的过程中尽量排除掉不能搜索到结果的分支

DFS一般就那几个参数

如果不是全局变量,DFS(答案合集,单个答案,条件,目标,计数)

第一种成员

class Solution {

   private List<List<Integer>> res = new ArrayList<>();

   public List<List<Integer>> combinationSum(int[] candidates, int target) {

       List<Integer> path = new ArrayList<>();

       

       Arrays.sort(candidates);

       backtrack(path,candidates,target,0,0);

       return res;

   }

   public void backtrack(List<Integer> path,int[] candidates, int target, int sum, int begin){

       if(sum==target){

           res.add(new ArrayList<>(path));

           return;

       }

       for(int i = begin; i < candidates.length; i ++){

           int rs = sum + candidates[i];

           if(sum<=target){

               path.add(candidates[i]);

               backtrack(path,candidates,target,rs,i);

               path.remove(path.size() - 1);


           }else{

               break;

           }

       }

   }

}

第二种非成员(推荐)-方法本类就是适用于各种情况

class Solution {

   public List<List<Integer>> combinationSum(int[] candidates, int target) {

       List<List<Integer>> ans = new ArrayList<List<Integer>>();

       List<Integer> combine = new ArrayList<Integer>();

       dfs(candidates, target, ans, combine, 0);

       return ans;

   }


   public void dfs(int[] candidates, int target, List<List<Integer>> ans, List<Integer> combine, int idx) {

       if (idx == candidates.length) {

           return;

       }

       if (target == 0) {

           ans.add(new ArrayList<Integer>(combine));

           return;

       }

       // 直接跳过

       dfs(candidates, target, ans, combine, idx + 1);

       // 选择当前数

       if (target - candidates[idx] >= 0) {

           combine.add(candidates[idx]);

           dfs(candidates, target - candidates[idx], ans, combine, idx);

           combine.remove(combine.size() - 1);

       }

   }

}

括号生成

分析

还是全排列的思想,但是有个限制:左右符号都等于n时才算数据,可以采用剪枝(左边大于右边时结束,因为是有效的,必须保证左括号先有)

class Solution {

   public List<String> generateParenthesis(int n) {

       List<String> res = new ArrayList<>();

       DFS("",0,0,res,n);

       return res;

   }

   public void DFS(String curStr,int left, int right, List<String> res, int n){

       if(left==n && right==n){

           res.add(curStr);

           return;

       }

       if(left<right)return;

       if(left<n){

           DFS(curStr+"(",left+1,right,res,n);

       }

       if(right<n){

           DFS(curStr+")",left,right+1,res,n);

       }

   }

}

寻找重复的子树

/**

* Definition for a binary tree node.

* public class TreeNode {

*     int val;

*     TreeNode left;

*     TreeNode right;

*     TreeNode() {}

*     TreeNode(int val) { this.val = val; }

*     TreeNode(int val, TreeNode left, TreeNode right) {

*         this.val = val;

*         this.left = left;

*         this.right = right;

*     }

* }

*/

class Solution {

   Map<String,TreeNode> coll = new HashMap<String,TreeNode>();

   Set<TreeNode> res = new HashSet<TreeNode>();

   public List<TreeNode> findDuplicateSubtrees(TreeNode root) {

       dfs(root);

       return new ArrayList<>(res);

   }

   public String dfs(TreeNode root){

       if(root==null)return "";

       StringBuilder sb = new StringBuilder();

       sb.append(root.val).append("(");

       sb.append(dfs(root.left)).append(")(");

       sb.append(dfs(root.right)).append(")");

       String serial = sb.toString();

       if(coll.containsKey(serial)){

           res.add(coll.get(serial));

       }else{

           coll.put(serial,root);

       }

       return serial;

   }

}

目录
相关文章
|
3月前
|
算法
DFS算法的实现
DFS算法的实现
58 3
|
5月前
|
C++
|
5月前
|
算法
DFS算法及应用(一)
DFS(深度优先搜索)是一种图遍历算法,常用于解决穷举问题,如全排列、迷宫问题、图的连通性等。它沿着树的深度分支进行探索,直至达到叶子节点,若无法继续则回溯。例如,将数字6拆分为3个正整数的递增序列问题可以通过DFS实现,类似地,分糖果问题和买瓜问题同样可以用DFS求解。DFS通常涉及递归或栈结构,通过标记已访问节点避免重复。在编程中,会定义递归函数,设定结束条件,然后枚举可能的情况,并处理下一层节点。
|
5月前
|
算法 索引
DFS算法及应用(二)
回溯:回溯就是DFS的一种,在搜索探索过程中寻找问题的解,当发现不满足求解条件时,就回溯返回,尝试其他路径。
|
机器学习/深度学习 人工智能 定位技术
|
算法
DFS and BFS
DFS and BFS
49 0
|
定位技术
DFS:踏青
DFS:踏青