DFS深度优先搜索—Java版

news2024/9/25 12:20:24

递归三要素

  • 递归的定义

  • 递归的拆解

  • 递归的出口

什么时候使用DFS?

  • 深度回溯问题(DFS与回溯区别不大)

  • 二叉树问题

  • 组合、排列问题

  • 找方案问题(解空间是一棵树或者图,需要自行构造图/树)

  • 图的搜索问题/路径/方案/节点 的的排列

不要使用DFS的场景

  • 连通块问题

  • 拓扑排序

  • 一切可以使用BFS解决的问题

组合问题

  • 例如,[1,2,3] 的所有组合为 [] [1] [2] [3] [1,2] [1,3] [2,3] [1,2,3] 共8种 。

    • 问题模型:求出所有满足条件的组合
    • 判断条件:组合中的元素与顺序无关
    • 时间复杂度:2^n
    • 难点:将题目的要求构成图或者树,以本题为例,可以将集合中的元素作为节点,那么如何构建边呢?为了避免出现出现12和21这种重复集合,可以让小数节点指向大数节点形成有向边。如下图所示:

    在这里插入图片描述
    除此之外也可以将其构建成一棵树,小节点指向大节点,如下所示: 在这里插入图片描述

DFS关键模板

    public int def(int x, int y ,int step){
        if(递归出口/达到目标状态){
            //进行对应操作
            return 0;
        }
        for (int i = 0; i < n; i++) {
            //遍历剩下的所有的情况
            if(visit[i]==0){
                //未访问
                x = 下一步更新;
                y = 下一步更新;
                visit[i] = 1;
                def(x,y,step);
                visit[i] = 0;  //记得回溯还原
            }
        }
    }

46. 全排列

class Solution {
    int n;
    List<List<Integer>> res;
    int[] visit;
    int[] permu;

    public void dfs(int[] nums,int step){
        if(step==n){ //存了10个数达到结束条件
            List<Integer> arr = new ArrayList<>();
            for(int a:permu){
                arr.add(a);
            }
            res.add(arr);
            return ;
        }

        for (int i = 0; i < nums.length; i++) {
            if(visit[i]==0){
                permu[step] = nums[i];
                visit[i] = 1;
                dfs(nums,step+1);
                visit[i] = 0;  //记得回溯
            }
        }

    }
    public List<List<Integer>> permute(int[] nums) {
        n = nums.length;
        res = new ArrayList<>();
        visit = new int[n]; 
        permu = new int[n]; //存一个结果
        dfs(nums,0); //0表示permu中存了0个数
        return res;
    }
}

以下题目DFS不一定是好的解法,但是练手深搜是非常合适的。所有,很多时候,其实DFS属于暴力搜索算法,并不是优化算法,但是作为最基础的搜索算法,必须掌握才能在此基础上进行动态规划或者剪枝优化。

386. 字典序排数

class Solution {
    int[] item;
    int[] visit;
    List<Integer> res;

    public void dfs(int[] nums,int step,int n){
        if(step==n){
            for(int a : item){
                res.add(a);
            }
            return;
        }

        for (int i = 0; i < n; i++) {
            if(visit[i]==0){
                visit[i]=1;
                item[step] = nums[i];
                //多了这段中途判断而已
                if(step>0 && ((item[step-1]+"").compareTo(item[step]+""))>0){
                    visit[i]=0;
                    return;
                }  
                dfs(nums,step+1,n);
                visit[i]=0;
            }
        }
    }
    public List<Integer> lexicalOrder(int n) {
        item = new int[n];
        visit = new int[n];
        res = new ArrayList<Integer>();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i]=i+1;
        }
        dfs(arr,0,n);
        return res;
    }
}

64. 最小路径和

这是一道非常非常典型的题目,DFS如果要求一个数,例如路径条数、方案数、路径总和等等。需要弄清楚,这个变量作为参数传递还是定义一个变量。如果作为参数传递,就是模板,直接在参数上进行加减操作,不影响该值在此循环的值。如果作为单独变量(第二种解法)需要复原变量,需要 sum -= grid[i+1][j] 复原,或者记录前面的值直接用,两种方法都一样。特别推荐直接将其作为参数进行传递。

import org.junit.Test;
import java.util.List;
public class lc64 {
    int res;
    int sum;
    int visit[][];
//==================第一种写法==================
    public void dfs(int[][] grid,int i,int j,int summ){
        if(i==(grid.length-1) && j==grid[0].length-1){
            res = Math.min(res,summ);
        }
        //下走
        if((i+1)<grid.length && visit[i+1][j]==0){
            visit[i+1][j]=1;  
            dfs(grid,i+1,j,summ+grid[i+1][j]);
            visit[i+1][j]=0; 
        }
        //右走
        if((j+1)<grid[0].length && visit[i][j+1]==0){
            visit[i][j+1]=1;  
            dfs(grid,i,j+1,summ+grid[i][j+1]);
            visit[i][j+1]=0; 
        }
    }

//================第二种写法================
    public void dfs(int[][] grid,int i,int j ){
        if(i==(grid.length-1) && j==grid[0].length-1){
            res = Math.min(res,sum);
        }
        //下走
        if((i+1)<grid.length && visit[i+1][j]==0){
            visit[i+1][j]=1;
            int temp = sum;
            sum += grid[i+1][j];
            dfs(grid,i+1,j );
            visit[i+1][j]=0;
            //或者 sum -= grid[i+1][j];
            sum = temp; // 还原sum; 
        }

        //右走
        if((j+1)<grid[0].length && visit[i][j+1]==0){
            visit[i][j+1]=1;
            int temp = sum;
            sum += grid[i][j+1];
            dfs(grid,i,j+1 );
            visit[i][j+1]=0;
            //或者 sum -= grid[i][j+1];
            sum = temp;// 还原sum;
        }
    }

    public int minPathSum(int[][] grid) {
        visit = new int[grid.length][grid[0].length];
        res = Integer.MAX_VALUE;
        sum = grid[0][0] ;
        dfs(grid,0,0 );
        return res;
    } 
}

其实,这道题也是非常好的记忆化搜索的动态规划例题,如下:

class Solution {
    int mem[][];
    public int arrive(int[][] grid,int i,int j){
        if(i==0 && j==0){
            return grid[i][j];
        }

        int v1 = Integer.MAX_VALUE;
        int v2 = Integer.MAX_VALUE;

        if((i-1)>=0 && j>=0 ) {
            if(mem[i-1][j]==-1){
                v1 = arrive(grid,i-1,j);
                mem[i-1][j] = v1;
            }else {
                v1 = mem[i-1][j];
            }
        }
        if((j-1>=0) && i>=0){
            if (mem[i][j-1]==-1){
                v2 = arrive(grid,i,j-1);
                mem[i][j-1] = v2;
            }else{
                v2 = mem[i][j-1];
            }
        }
        return Math.min(v1,v2)+grid[i][j];
    }

    public int minPathSum(int[][] grid) {  
        //动态规划
        mem = new int[grid.length][grid[0].length];
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                mem[i][j] = -1;
            }
        }
        mem[0][0] = grid[0][0];
        return arrive(grid,grid.length-1,grid[0].length-1);

    }
}

最后以一道典型DFS题结束本章讲解

200. 岛屿数量

思路:凡是搜到了一个1,就找到了一个岛屿,为了避免重复计算,需要把这个岛(这个岛不是这个图)的所有1改成0,然后继续往下搜。简单说就是看见1就计数+1,然后把这片岛毁了,接着往下走!其实这里不用visit记录是否访问过,因为访问过的会将其标记为0,但是写了无妨!建议按照模板操作!

public class lc200 {
    int visit[][];
    public void dfs(char[][] grid,int i , int j){
        if(grid[i][j]=='0'){
            //如果是水就不用深入查找了
            return;
        }
        grid[i][j]='0'; //摧毁
        int[][] dirc = new int[][]{{-1,0},{1,0},{0,-1},{0,1}}; //方向 上下左右

        for (int k = 0; k < dirc.length; k++) { //往四个方向走
            int x = dirc[k][0];
            int y = dirc[k][1];
            //往x,y指定的方向走,判断符合条件才走
            if((((i+x)<grid.length)&&(i+x)>=0) &&
                    (((j+y)<grid[0].length) && j+y>=0) &&
                    visit[i+x][j+y]==0){ //这里判断写的复杂,就是边界判断加访问判断

                visit[i+x][j+y] = 1;
                if(grid[i+x][j+y]=='1'){
                    dfs(grid,i+x,j+y); //如果还是岛就继续深入
                }
                visit[i+x][j+y] = 0;
            }
        }
    }
    public int numIslands(char[][] grid) {
        int count = 0;
        visit = new int[grid.length][grid[0].length];
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if(grid[i][j]=='1'){
                    count++;
                    dfs(grid,i,j); //开始毁灭这个岛所有1
                }
            }
        }
        return count;
    }
}

推荐LeetCode类似题型

463. 岛屿的周长

思路:这道题只有一个岛屿,所以可以两重循环判断1是否挨着0或者是边界,是的话就算作边,考虑上下左右,加起来就是周长。但是 如果深度搜索呢?一样的,对于每个1都计算与水或者边界相邻的边。

695. 岛屿的最大面积

思路:和统计岛屿数量相同,只不过深度遍历每个岛屿时计算有多少个1,存下来,最后返回最大值即为最大面积的岛屿。

827. 最大人工岛

以上,此题作为思考题!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Smokeping的主从模式部署

Smokeping 支持 Standalone&#xff08;单机&#xff09;模式和 Master/Slave&#xff08;主从&#xff09;模式。 之前老苏折腾过单机模式&#xff0c;这次应网友 Roxmie 的要求&#xff0c;研究了一下主从模式的部署 文章传送门&#xff1a; 网络性能监控工具Smokeping 因为…

Go基础-环境安装

文章目录1 Go?Golang?2 下载Go3 windows安装4 测试是否成功1 Go?Golang? Go也称为Golang&#xff0c;是Google开发的一个开源的编译型的静态语言。 Golang的主要关注点是高可用、高并发和高扩展性&#xff0c;Go语言定位是系统级编程语言&#xff0c;对web程序具有很好的支…

SAP数据导入工具(LSMW) 超级详细教程(批量导入内部订单)

目录 第一步&#xff1a;记录批导步骤编辑数据源对应字段 第二步&#xff1a;维护数据源 第三步&#xff1a;维护数据源对应字段&#xff08;重要&#xff09; 第四步&#xff1a;维护数据源关系。 第五步&#xff1a;维护数据源与导入字段的对应关系。 第六步&#xff0…

K_A12_006 基于STM32等单片机驱动BH1750模块 串口与OLED0.96双显示

K_A12_006 基于STM32等单片机驱动BH1750模块 串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明时序对应程序:四、部分代码说明1、接线引脚定义1.1、STC89C52RCBH1750模块1.2、STM32F103C8T6BH1750模块五、基础知识学习与相关资料下载六、视频效果展示与程…

《蓝桥杯每日一题》递归·AcWing 1497. 树的遍历

1.题目描述一个二叉树&#xff0c;树中每个节点的权值互不相同。现在给出它的后序遍历和中序遍历&#xff0c;请你输出它的层序遍历。输入格式第一行包含整数 N&#xff0c;表示二叉树的节点数。第二行包含 N个整数&#xff0c;表示二叉树的后序遍历。第三行包含 N 个整数&…

设计模式之迭代器模式与命令模式详解和应用

目录1 迭代器模式1.1 目标1.2 内容定位1.3 迭代器模式1.4 迭代器模式的应用场景1.5 手写字定义的送代器1.6 迭代器模式在源码中的体现1.7 迭代器模式的优缺点2 命令模式2.1 定义2.2 命令模式的应用场景2.3 命令模式在业务场景中的应用2.4 命令模式在源码中的体现2.5 命令模式的…

UVa 211 The Domino Effect 多米诺效应 暴力搜索

题目链接&#xff1a;UVa 211 The Domino Effect 题目描述&#xff1a; 一张多米诺骨牌拥有两个数值&#xff0c;一共有二十八张不同的多米诺骨牌&#xff0c;这二十八张多米诺骨牌的点数如下图所示&#xff1a; 上图的BoneBoneBone代表编号&#xff0c;而PipsPipsPips代表两个…

Springboot扩展点系列之终结篇:Bean的生命周期

前言关于Springboot扩展点系列已经输出了13篇文章&#xff0c;分别梳理出了各个扩展点的功能特性、实现方式和工作原理&#xff0c;为什么要花这么多时间来梳理这些内容&#xff1f;根本原因就是这篇文章&#xff1a;Spring bean的生命周期。你了解Spring bean生命周期&#xf…

前端最全面试题整理

前端基础 一、 HTTP/HTML/浏览器 1、说一下 http 和 https https 的 SSL 加密是在传输层实现的。 (1) http 和 https 的基本概念 http: 超文本传输协议&#xff0c;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;T…

操作SSH无密登录配置

例如小编有三台服务器需要相互访问&#xff0c;就需要配置三台&#xff0c;这三台分别是hadoop102,hadoop103 , hadoop1041.打开三个服务器&#xff0c;分别生成hadoop102&#xff0c;hadoop103 , hadoop104的公钥和私钥输入命令&#xff0c;然后一直回车&#xff0c;这时候什么…

狂神聊Redis复习笔记二

目录事务监控&#xff01; Watch &#xff08;面试常问&#xff01;&#xff09;悲观锁&#xff1a;乐观锁&#xff1a;Redis测监视测试Redis.conf详解Redis持久化RDB&#xff08;Redis DataBase&#xff09;AOF&#xff08;Append Only File&#xff09;Redis发布订阅Redis主从…

Docker资源隔离(namespace,cgroups)

一、概述 Docker容器的本质是宿主机上的一个进程。Docker通过namespace实现了资源隔离&#xff0c;通过cgroups实现了资源限制&#xff0c;通过写时复制机制&#xff08;copy-on-write&#xff09;实现了高效的文件操作。 二、Linux内核的namespace机制 namespace 机制提供一种…

jfr引起的一次jvm异常记录

业务生产启动时&#xff0c;20个节点有1-2个节点因为jvm问题出现启动失败&#xff0c;k8s自动重启后正常。在测试环境2个节点下偶现 排查思路&#xff1a; 先拿到hs_err_pid的jvm错误文件找到当前线程和内部错误信息 hs_err_pid 文件分析 当前线程&#xff1a;lettuce的线程…

使用Robot Framework实现多平台自动化测试

目录 前言 1、设计目标 2、架构设计 3、平台实现 4、平台的创新点 5、平台的实施效果 6、总结 重点&#xff1a;配套学习资料和视频教学 前言 基于Robot Framework、Jenkins、Appium、Selenium、Requests、AutoIt等开源框架和技术&#xff0c;成功打造了通用自动化测试…

各数据库数据类型的介绍和匹配

各数据库数据类型的介绍和匹配1. Oracle的数据类型2. Mysql的数据类型3. Sql server的数据类型4. 类型匹配5. Awakening1. Oracle的数据类型 数据类型介绍 VARCHAR2 :可变长度的字符串 最大长度4000 bytes 可做索引的最大长度749&#xff1b; NCHAR :根据字符集而定的固定长度字…

微服务架构的演变

文章目录1.1 系统架构的演变过程1.1.1 单体应用架构1.1.2 垂直应用架构1.1.3 分布式架构1.1.4 SOA架构1.1.5 微服务架构1.2 微服务架构设计原则1.2.1 AKF拆分原则1.2.1.1 X轴扩展&#xff08;水平复制&#xff09;1.2.1.2 Y轴扩展&#xff08;模块拆分&#xff09;1.2.1.3 Z轴扩…

【SSM】Spring对IoC的实现方式DI详讲

控制反转的一种实现方式——依赖注入一、IoC 控制反转&#xff08;Overview&#xff09;依赖注入&#xff08;DI&#xff09;- Overview利用 IoC&#xff08;控制反转&#xff09;这种思想有什么好处呢&#xff1f;二、依赖注入的方式setter 方式&#xff08;xml配置中的proper…

Java JSR规范列表

Java JSR规范列表目录概述需求&#xff1a;设计思路实现思路分析1.JSR2.JSR方法3.web service4.Webservice:5.数据处理器拓展实现参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,m…

JavaWeb-连接数据库实现用户登录、注册、修改密码(全代码)

上一篇博客中我通过使用HttpServlet完成一个假登录&#xff0c;这篇博客我将通过JDBC连接数据库&#xff0c;使其实现简单的用户登录、注册以及更改密码一、MySQL:MySQL部分代码&#xff1a;-- ---------------------------- -- Table structure for users -- ----------------…

WSL(ubuntu2204)使用xfce4桌面打不开语言支持及配置WSL服务自启

语言支持报错 在图形桌面或命令行打开语言支持报错&#xff1a;dbus.exceptions.DBusException: org.freedesktop.DBus.Error.FileNotFound: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory itboonelocalhost:/$ sudo /usr/bin/gnome-…