leetcode之DFS+BFS+DSU刷题总结1

leetcode之dfs+bfs+dsu刷题总结1

1-所有可能的路径
题目链接:题目链接戳这里!!!

思路:dfs搜索所有路径,并用栈记录搜索轨迹。

class Solution {
    Stack<Integer> stack = new Stack<>() ;
    List<List<Integer>> list = new ArrayList<>() ;
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        dfs(0,graph) ;
        return list ;
    }
    public void dfs(int x, int [][] graph){
        stack.push(x) ;
        if(x==graph.length-1){
            list.add(new ArrayList<>(stack)) ;
        }
        for(int y : graph[x]){
            dfs(y,graph) ;
        }
        stack.pop() ;
    }
}

在这里插入图片描述
2-判断二分图
题目链接:题目链接戳这里!!!

思路:dfs+染色法。

我们使用图搜索算法从各个连通域的任一顶点开始遍历整个连通域,遍历的过程中用两种不同的颜色对顶点进行染色,相邻顶点染成相反的颜色。这个过程中倘若发现相邻的顶点被染成了相同的颜色,说明它不是二分图;反之,如果所有的连通域都染色成功,说明它是二分图。

class Solution {
    public boolean isBipartite(int[][] graph) {
        int [] vis = new int [graph.length] ;
        for(int i=0; i<graph.length; i++){
            if(vis[i]==0 && !dfs(graph,i,1,vis)){
                return false ;
            }
        }
        return true ;
    }
    public boolean dfs(int [][] graph, int x, int color, int [] vis){
        if(vis[x]!=0){
            return vis[x] == color ;
        }
        vis[x] = color ;
        for(int y : graph[x]){
            if(!dfs(graph,y,-color,vis)){
                return false ;
            }
        }
        return true ;

    }
}

在这里插入图片描述
思路2:bfs+染色法
对于每个顶点,如果未被访问过,则入队,对于该顶点的所有邻接顶点,如果已经被染色,且颜色与当前颜色相同,则不是二分图,如果未被染色,则应染成和相邻顶点相同的颜色。

class Solution {
    public boolean isBipartite(int[][] graph) {
        int [] vis = new int [graph.length] ;
        Queue<Integer> queue = new LinkedList<>() ;
        for(int i=0; i<graph.length; i++){
            if(vis[i]!=0){
                continue ;
            }
            queue.add(i) ;
            vis[i] = 1 ;
            while(!queue.isEmpty()){
                int x = queue.poll() ;
                for(int y : graph[x]){
                    if(vis[y]==vis[x]){
                        return false ;
                    }
                    if(vis[y]==0){
                        vis[y] = -vis[x] ;
                        queue.add(y) ;
                    }
                }
            }
        }
        return true ; 
    }
}

在这里插入图片描述
3-地图中的最高点
题目链接:题目链接戳这里!!!

思路:多源bfs
题目要求让矩阵中的最高高度最大,我们可以通过最大化每个格子的高度来做到这一点。由于任意相邻的格子高度差至多为 1,这意味着对于每个格子,其高度至多比其相邻格子中的最小高度多 1。

题目要求水域的高度必须为 0,因此水域的高度是已经确定的值,我们可以从水域出发,推导出其余格子的高度:

首先,计算与水域相邻的格子的高度。对于这些格子来说,其相邻格子中的最小高度即为水域的高度 0,因此这些格子的高度为 1。
然后,计算与高度为 1 的格子相邻的、尚未被计算过的格子的高度。对于这些格子来说,其相邻格子中的最小高度为 1,因此这些格子的高度为 2。
以此类推,计算出所有格子的高度。

记录下所有水域的位置,然后执行广度优先搜索,计算出所有陆地格子的高度,即为答案。

class Solution {
    int [] dx = {-1,1,0,0} ;
    int [] dy = {0,0,-1,1} ;
    public int[][] highestPeak(int[][] isWater) {
        int [][] ans = new int [isWater.length][isWater[0].length] ;
        for(int i=0; i<ans.length; i++){
            Arrays.fill(ans[i], -1) ; //初始化所有顶点未被访问
        }
        Queue<int[]> queue = new ArrayDeque<int[]>() ;
        for(int i=0; i<isWater.length; i++){
            for(int j=0; j<isWater[0].length; j++){
                if(isWater[i][j]==1){
                    ans[i][j] = 0 ;
                    queue.add(new int []{i,j}) ;
                }
            }
        }
        while(!queue.isEmpty()){
            int [] p = queue.poll() ;
            for(int i=0; i<4; i++){
                int tx = p[0] + dx[i] ;
                int ty = p[1] + dy[i] ;
                if(tx>=0 && ty>=0 && tx<isWater.length && ty<isWater[0].length && ans[tx][ty]==-1){
                    ans[tx][ty] = ans[p[0]][p[1]] + 1 ;
                    queue.add(new int []{tx,ty}) ;

                }
            }
        }
        return ans ;
    }
}

在这里插入图片描述
4-所有路径
题目链接:题目链接戳这里!!!

思路:dfs+栈记录
从0号顶点出发,依次搜索相邻顶点,并用栈记录搜索轨迹。

class Solution {
    List<List<Integer>> list = new ArrayList<>() ;
    Stack<Integer> stack = new Stack<>() ;
    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        dfs(graph,0) ;
        return list ;
    }
    public void dfs(int [][] graph, int x){
        stack.push(x) ;
        if(x==graph.length-1){
            list.add(new ArrayList<>(stack)) ;
        }
        for(int y : graph[x]){
            dfs(graph,y) ;
        }
        stack.pop() ;
    }
}

在这里插入图片描述
5-多余的边
题目链接:题目链接戳这里!!!

思路:并查集
在一棵树中,边的数量比节点的数量少 1。如果一棵树有 n 个节点,则这棵树有 n−1 条边。这道题中的图在树的基础上多了一条边,因此边的数量也是 n。

树是一个连通且无环的无向图,在树中多了一条边之后就会出现环,因此多余的边即为导致环出现的边。

可以通过并查集寻找多余的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。

如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。

如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为多余的边,将当前的边作为答案返回。

class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length ;
        int [] parent = new int [n+1] ;
        for(int i=1; i<=n; i++){
            parent[i] = i ;
        }
        for(int i=0; i<n; i++){
            int [] edge = edges[i] ;
            int node0 = edge[0], node1 = edge[1] ;
            if(find(parent,node0)!=find(parent,node1)){
                union(parent,node0,node1) ;
            }else{
                return edge ;
            }
        }
        return new int []{} ;
    }
    public int find(int [] parent, int node){
        if(node!=parent[node]){
            parent[node] = find(parent,parent[node]) ;
        }
        return parent[node] ;
    }
    public void union(int [] parent, int node1, int node0){
        parent[parent[node1]] = find(parent,parent[node0]) ;
    }
}

在这里插入图片描述
6-矩阵中的距离
题目链接:题目链接戳这里!!!

思路:bfs
首先将所有的0入队,然后多元广度优先搜索,如果其它相邻点未搜索过,则为当前点加1.

class Solution {
    int [] dx = {-1,1,0,0} ;
    int [] dy = {0,0,-1,1} ;
    public int[][] updateMatrix(int[][] mat) {
        //广度优先搜索
        int m = mat.length ;
        int n = mat[0].length ;
        Queue<int[]> queue = new LinkedList<int[]>() ;
        int [][] ans = new int [m][n] ;
        boolean [][] vis = new boolean [m][n] ;
        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if(mat[i][j] == 0){
                    queue.add(new int [] {i,j}) ;
                    vis[i][j] = true ;
                }
            }
        }
        while(!queue.isEmpty()){
            int [] edge = queue.poll() ;
            for(int i=0; i<4; i++){
                int tx = edge[0] + dx[i] ;
                int ty = edge[1] + dy[i] ;
                if(tx>=0 && ty>=0 && tx<m && ty<n && !vis[tx][ty]){
                    ans[tx][ty] = ans[edge[0]][edge[1]] + 1 ;
                    queue.add(new int []{tx,ty}) ;
                    vis[tx][ty] = true ;
                }
            }
        }
        return ans ;
    }
}

在这里插入图片描述
7-最小基因变化
题目链接:题目链接戳这里!!!

思路:bfs+剪枝
题目给定一个起始字符串start和一个目标字符串end, 其中start只能通过以下步骤变化为新的字符串。

每一次变化,都只能用"A", “C”, “G”, "T"里的任意一个字母替换当前字符串里的某一个字母。
替换后得到的新的字符串必须在给定的bank里。
明确这两点之后,我们很容易地想到这个题建模为树(具体来说,无权树),可知只不过这个树的子树会比较多,可以剪枝。

class Solution {
    public int minMutation(String start, String end, String[] bank) {
        Set<String> bankSet = new HashSet<>() ;
        for(String str : bank){
            bankSet.add(str) ;
        }
        String [] gene = {"A","C","G","T"} ;
        Set<String> vis = new HashSet<>() ;
        Queue<String> queue = new LinkedList<>() ;
        vis.add(start) ;
        queue.add(start) ;
        int step = 0 ;
        while(!queue.isEmpty()){
            int size = queue.size() ;
            for(int k=0; k<size; k++){
            String s = queue.poll() ;
            if(s.equals(end)){
                return step ;
            }
            for(int i=0; i<s.length(); i++){
                StringBuilder cur = new StringBuilder(s) ;
                for(int j=0; j<gene.length; j++){
                   String ans =  cur.replace(i,i+1,gene[j]).toString() ;
                    if(!vis.contains(ans) && bankSet.contains(ans)){
                        queue.add(ans) ;
                        vis.add(ans) ;
                    }
                }
            }
         }
            step ++ ;
        }
        return -1 ;
    }
}

在这里插入图片描述
8-打开转盘锁
题目链接:题目链接戳这里!!!

思路:bfs+剪支
我们可以使用广度优先搜索,找出从初始数字 0000 到解锁数字target 的最小旋转次数。

具体地,我们在一开始将 0000 加入队列,并使用该队列进行广度优先搜索。在搜索的过程中,设当前搜索到的数字为 status,旋转的次数为 step,我们可以枚举 status 通过一次旋转得到的数字。设其中的某个数字为 next,如果其没有被搜索过,我们就将 next加入队列。如果搜索到了target,我们就返回其对应的旋转次数。

为了避免搜索到死亡数字,我们可以使用哈希表存储 deadends 中的所有元素,这样在搜索的过程中,我们可以均摊 O(1)地判断一个数字是否为死亡数字。同时,我们还需要一个哈希表存储所有搜索到的状态,避免重复搜索。

如果搜索完成后,我们仍没有搜索到 target,说明我们无法解锁,返回 -1。

class Solution {
    public int openLock(String[] deadends, String target) {
        if("0000".equals(target)){
            return 0 ;
        }
        Set<String> deadSet = new HashSet<>() ;
        for(String str : deadends){
            deadSet.add(str) ;
        }
        Set<String> vis = new HashSet<>() ;
        Queue<String> queue = new LinkedList<>() ;
        vis.add("0000") ;
        queue.add("0000") ;
        if(deadSet.contains("0000")){
            return -1 ;
        }

        int step=0;
        while(!queue.isEmpty()){
            step ++  ;
            int size = queue.size() ;
            for(int i=0; i<size; i++){
                String status = queue.poll() ;
                for(String next : get(status)){
                    if(!vis.contains(next) && !deadSet.contains(next)){
                        if(next.equals(target)){
                            return step ;
                        }
                        queue.add(next) ;
                        vis.add(next) ;
                    }
                }
            }
        }
        return -1 ;
    }
    public char f1(char ch){
        return ch=='9' ? '0' : (char)(ch+1) ;
    }
    public char f2(char ch){
        return ch=='0' ? '9' : (char)(ch-1) ;
    }
    public List<String> get(String s){
        List<String> res = new ArrayList<>() ;
        char [] c = s.toCharArray() ;
        for(int i=0; i<c.length;i++){
            char num = c[i] ;
            c[i] = f1(c[i]) ;
            res.add(new String(c)) ;
            c[i] = f2(num) ;
            res.add(new String(c)) ;
            c[i] = num ;
        }
        return res ;
    }
}

在这里插入图片描述
9-连通网络的操作次数
题目链接:题目链接戳这里!!!

思路1:并查集
我们可以使用并查集来得到图中的连通分量数。
并查集本身就是用来维护连通性的数据结构。如果其包含 n 个节点,那么初始时连通分量数为 n,每成功进行一次合并操作,连通分量数就会减少 1。

将所有已经联通的合并为一个集合,每次访问每个元素的根节点,如果不是它本身,联通分量就累加1,联通分量数减1就是答案。

class Solution {
    public int makeConnected(int n, int[][] connections) {
        if(n-1>connections.length){
            return -1 ;
        }
        //并查集
        int [] parent = new int [n+1] ;
        for(int i=1; i<=n; i++){
            parent[i]  = i ;
        }
        for(int [] connection : connections){
            union(parent,connection[0]+1,connection[1]+1) ;
        }
        int cnt = 0 ;
        for(int i=1; i<=n; i++){
            if(i==parent[i]){
                cnt ++ ;
            }
        }
        return cnt - 1 ;
    }
    public int find(int [] parent, int x){
        if(x!=parent[x]){
            parent[x] = find(parent,parent[x]) ;
        }
        return parent[x] ;
    }
    public void union(int [] parent, int x, int y){
        int root1 = find(parent,x) ;
        int root2 = find(parent,y) ;
        if(root1==root2){
            return ;
        }
        parent[root1] = root2 ;
    }
}

在这里插入图片描述
思路2:dfs
建立双向邻接表,然后深度优先搜索,统计联通分量个数,联通分量个数减去1就是答案。然而深搜的效率比并查集低很多。

class Solution {
    public int makeConnected(int n, int[][] connections) {
        if(n-1>connections.length){
            return -1 ;
        }
        List<List<Integer>> edges = new ArrayList<>() ;
        for(int i=0; i<n; i++){
            edges.add(new ArrayList<>()) ;
        }
        for(int [] connection : connections){
            edges.get(connection[0]).add(connection[1]) ;
            edges.get(connection[1]).add(connection[0]) ;
        }
        int ans = 0 ;
        boolean [] vis = new boolean[n] ;
        for(int i=0; i<n; i++){
            if(!vis[i]){
            dfs(edges,i,vis) ;
            ans ++ ;
            }
        }
        return ans - 1 ;
    }
    public void dfs(List<List<Integer>> edges, int x, boolean [] vis){
        vis[x] = true ;
        for(int y : edges.get(x)){
            if(!vis[y]){
                dfs(edges,y,vis) ;
            }
        }
    }
}

在这里插入图片描述
10-开锁密码
题目链接:题目链接戳这里!!!

思路:bfs+剪支
我们可以使用广度优先搜索,找出从初始数字 0000 到解锁数字target 的最小旋转次数。

具体地,我们在一开始将 0000 加入队列,并使用该队列进行广度优先搜索。在搜索的过程中,设当前搜索到的数字为 str,旋转的次数为 step,我们可以枚举 str 通过一次旋转得到的数字。设其中的某个数字为 next,如果其没有被搜索过,我们就将 next加入队列。如果搜索到了target,我们就返回其对应的旋转次数。

为了避免搜索到死亡数字,我们可以使用哈希表存储 deadends 中的所有元素,这样在搜索的过程中,我们可以均摊 O(1)地判断一个数字是否为死亡数字。同时,我们还需要一个哈希表存储所有搜索到的状态,避免重复搜索。

如果搜索完成后,我们仍没有搜索到 target,说明我们无法解锁,返回 -1。

class Solution {
    public int openLock(String[] deadends, String target) {
        //广度优先搜索
        if("0000".equals(target)){
            return 0 ;
        }
        Set<String> deadSet = new HashSet<>() ;
        for(int i=0; i<deadends.length; i++){
            deadSet.add(deadends[i]) ;
        }
        Set<String> vis = new HashSet<>() ;
        Queue<String> queue = new LinkedList<>() ;
        vis.add("0000") ;
        queue.add("0000") ;
        if(deadSet.contains("0000")){
            return -1 ;
        }
        int step = 0 ;
        while(!queue.isEmpty()){
            step ++ ;
            int size = queue.size() ;
            for(int i=0; i<size; i++){
                String str = queue.poll() ;
                for(String next : get(str)){
                    if(!vis.contains(next) && !deadSet.contains(next)){
                    if(next.equals(target)){
                        return step ;
                    }
                    queue.add(next) ;
                    vis.add(next) ;
                }
                }
            }
        }
        return -1 ;
    }
    public List<String> get(String s){
        char [] c = s.toCharArray() ;
        List<String> ans = new ArrayList<>() ;
        for(int i=0; i<c.length; i++){
            char num = c[i] ;
            c[i] = f1(c[i]) ;
            ans.add(new String(c)) ;
            c[i] = f2(num) ;
            ans.add(new String(c)) ;
            c[i] = num ;
        }
        return ans ;
    }
    public char f1(char ch){
        return ch=='9' ? '0' : (char)(ch+1) ;
    }
    public char f2(char ch){
        return ch=='0' ? '9' : (char)(ch-1) ;
    }
}

在这里插入图片描述
11-颜色填充
题目链接:题目链接戳这里!!!

思路1:dfs+标记
如果四个方向满足条件,就沿着四个方向搜索,并涂色。

class Solution {
    int [] dx = {-1,1,0,0} ;
    int [] dy = {0,0,-1,1} ;
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        
        dfs(image,sr,sc,newColor,image[sr][sc]) ;
        return image ;
    }
    public void dfs(int [][] image, int x, int y, int newColor, int n){
        image[x][y] = newColor ;
        for(int i=0; i<4; i++){
            int tx = x + dx[i] ;
            int ty = y + dy[i] ;
            if(tx>=0 && ty>=0 && tx<image.length && ty<image[0].length && image[tx][ty]==n && image[tx][ty]!=newColor){
                dfs(image,tx,ty,newColor,n) ;
            }
        }
    }
}

在这里插入图片描述
思路2:bfs
将第一个坐标位置涂色,并将坐标入队,每次沿着四个方向涂色即可。

class Solution {
    int [] dx = {-1,1,0,0} ;
    int [] dy = {0,0,-1,1} ;
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        Queue<int[]> queue = new LinkedList<int[]>() ;
        int n = image[sr][sc] ;
        image[sr][sc] = newColor ;
        queue.add(new int []{sr,sc}) ;
        while(!queue.isEmpty()){
            int [] a = queue.poll() ;
            for(int i=0; i<4; i++){
                int tx = a[0] + dx[i] ;
                int ty = a[1] + dy[i] ;
                if(tx>=0 && ty>=0 && tx<image.length && ty<image[0].length && image[tx][ty] != newColor && image[tx][ty]==n){
                    image[tx][ty] = newColor ;
                    queue.add(new int []{tx,ty}) ;
                }
            }
        }
        return image ;

    }
}

在这里插入图片描述

日拱一卒无有即尽,功不唐娟终入海,稳住,少年!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nuist__NJUPT

给个鼓励吧,谢谢你

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值