算法题总结(十九)——图论

图论

DFS框架

void dfs(参数) {
if (终止条件) {
    存放结果;
    return;
}

for (选择:本节点所连接的其他节点) {
    处理节点;
    dfs(图,选择的节点); // 递归
    回溯,撤销处理结果
}
}

深搜三部曲

  1. 确认递归函数,参数
  2. 确认终止条件
  3. 处理目前搜索节点出发的路径

797、所有可能的路径

给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例 1:

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3

1、确认递归函数、参数

首先我们dfs函数一定要存一个图,用来遍历的,还要存一个目前我们遍历的节点,定义为x。

至于路径和路径集合,可以放在全局变量里。

2、确认终止条件

当目前遍历的节点 为 最后一个节点的时候,就找到了一条,从 出发点到终止点的路径。

3、处理目前搜索节点出发的路径

class Solution {
    List<List<Integer>> res =new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public void DFS(int[][] graph,int x)
    {
        //说明找到了终点
        if(x==graph.length-1)
        {
            res.add(new ArrayList<>(path));  //这里一定要建立一个新的
            return;
        }
        //遍历和x相连的结点
        for(int i=0;i<graph[x].length;i++)
        {
            path.add(graph[x][i]);
            DFS(graph,graph[x][i]);
            path.remove(path.size()-1);  //回溯
        }
    }


    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        //从头结点开始
        path.add(0);
        DFS(graph,0);
        return res;
    }
}

BFS框架

广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。

当然,也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行

200、岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

思路

本题思路,遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。

在遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。

那么如何把节点陆地所能遍历到的陆地都标记上呢,就可以使用 DFS,BFS或者并查集

DFS:

class Solution {
    boolean[][] visited;  //定义成全局的,不用传递
    int dir[][]={{0,1},{0,-1},{1,0},{-1,0}};  //右、左、下、上

    //把与某陆地结点相邻的陆地都标记上
    public void DFS(char[][] grid,int x,int y)
    {   
        if(visited[x][y]==true||grid[x][y]=='0') //如果被访问过或者是水
            return;

        visited[x][y]=true;
        for(int i=0;i<4;i++){ //四个方向遍历一遍
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                continue;
            //也可以把上面的放这里,if(visited[x][y]==false && grid[x][y]=='1')
            DFS(grid,nx,ny);
        }

    }
    public int numIslands(char[][] grid) {
        int count=0;
        visited=new boolean[grid.length][grid[0].length];
        //遇到一个没有遍历过的节点陆地,计数器就加一
        //然后把该节点陆地所能遍历到的陆地都标记上。
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(visited[i][j]==false && grid[i][j]=='1')
                {
                    count++;
                    DFS(grid,i,j);
                }
            }
        }
        return count;

    }
}

BFS

不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节:

根本原因是只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过

很多同学可能感觉这有区别吗?

如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。

class Solution {
    boolean[][] visited;  //定义成全局的,不用传递
    int dir[][]={{0,1},{0,-1},{1,0},{-1,0}};  //右、左、下、上

    //把与某陆地结点相邻的陆地都标记上
    public void BFS(char[][] grid,int x,int y)
    {   
        Deque<int[]> queue=new ArrayDeque<>();
        queue.offer(new int[]{x,y});
        visited[x][y]=true; //只要入队就标记
        while(!queue.isEmpty())
        {
            int[] cur=queue.poll();
            int cx=cur[0];
            int cy=cur[1];
            for(int i=0;i<4;i++)
            {
                int nx=cx+dir[i][0];
                int ny=cy+dir[i][1];
                if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                    continue;
                if(visited[nx][ny]==false && grid[nx][ny]=='1')
                {   queue.offer(new int[]{nx,ny});
                 visited[nx][ny]=true;
                }
            }
        }    

    }
    public int numIslands(char[][] grid) {
        int count=0;
        visited=new boolean[grid.length][grid[0].length];
        //遇到一个没有遍历过的节点陆地,计数器就加一
        //然后把该节点陆地所能遍历到的陆地都标记上。
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(visited[i][j]==false && grid[i][j]=='1')
                {
                    count++;
                    BFS(grid,i,j);
                }
            }
        }
        return count;

    }
}

695、岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。

岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

岛屿的面积是岛上值为 1 的单元格的数目。

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

示例 1:

输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

DFS:

设置一个全局变量count,每次DFS的时候++,遍历的时候把count置为0

class Solution {
    boolean[][] visited;
    int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};
    int count;

    public void DFS(int[][] grid,int x,int y)
    {   
        if(visited[x][y]==true ||grid[x][y]==0)
            return;
        visited[x][y]=true;
        count++;
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                continue;
            DFS(grid,nx,ny);
        }
    }

    public int maxAreaOfIsland(int[][] grid) {
        visited=new boolean[grid.length][grid[0].length];
        int result=0;  //记录最大值
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(visited[i][j]==false && grid[i][j]==1)
                {
                    count=0; //每次找的时候,赋值为0
                    DFS(grid,i,j);
                    result=Math.max(result,count);
                }
            }
        }
        return result;

    }
}

BFS

class Solution {
    boolean[][] visited;
    int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};
    int count;

    public void BFS(int[][] grid,int x,int y)
    {   
        Deque<int[]> queue=new ArrayDeque<>();
        queue.offer(new int[]{x,y});
        visited[x][y]=true;
        count++;
        while(!queue.isEmpty())
        {
            int [] cur=queue.poll();
            int cx=cur[0];
            int cy=cur[1];
            for(int i=0;i<4;i++)
            {
                int nx=cx+dir[i][0];
                int ny=cy+dir[i][1];
                if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                    continue;
                if(visited[nx][ny]==false && grid[nx][ny]==1)
                {
                    queue.offer(new int[]{nx,ny});
                    visited[nx][ny]=true;
                    count++;
                }
            }

        }     
    }

    public int maxAreaOfIsland(int[][] grid) {
        visited=new boolean[grid.length][grid[0].length];
        int result=0;  //记录最大值
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(visited[i][j]==false && grid[i][j]==1)
                {
                    count=0; //每次找的时候,赋值为0
                    BFS(grid,i,j);
                    result=Math.max(result,count);
                }
            }
        }
        return result;

    }
}

1020、飞地的数量

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

示例 1:

输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。

示例 2:

输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。

思路:

本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图的时候,统计此时还剩下的陆地就可以了,所以不需要使用visited数组。

DFS:

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    public void DFS(int[][] grid,int x,int y)
    {
        if(grid[x][y]==0)
            return;
        grid[x][y]=0;   //访问一个陆地,就把他变成海洋
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                continue;
            DFS(grid,nx,ny);
        }

    }
    public int numEnclaves(int[][] grid) {
        int count=0;
        //遍历边界,如果边界是陆地,就使用dfs把相邻的变成海洋
        for(int i=0;i<grid.length;i++)
        {
            if(grid[i][0]==1)
                DFS(grid,i,0);
            if(grid[i][grid[0].length-1]==1)
                DFS(grid,i,grid[0].length-1);
        }

        for(int j=1;j<grid[0].length-1;j++)
        {
            if(grid[0][j]==1)
                DFS(grid,0,j);
            if(grid[grid.length-1][j]==1)
                DFS(grid,grid.length-1,j);
        }
        //计算陆地单元格的数量
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(grid[i][j]==1)
                    count++;
            }
        }
        return count;


    }
}

BFS:

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    public void BFS(int[][] grid,int x,int y)
    {
        Queue<int[]> queue =new LinkedList<>();
        queue.offer(new int[]{x,y});
        grid[x][y]=0;// 把陆地变为海洋
        while(!queue.isEmpty())
        {
            int[] cur=queue.poll();
            int cx=cur[0];
            int cy=cur[1];
            for(int i=0;i<4;i++)
            {
                int nx=cx+dir[i][0];
                int ny=cy+dir[i][1];
                if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                    continue;
                if(grid[nx][ny]==1)
                {
                    queue.offer(new int[]{nx,ny});
                    grid[nx][ny]=0;   // 把陆地变为海洋
                }

            } 
        }


    }
    public int numEnclaves(int[][] grid) {
        int count=0;
        //遍历边界,如果边界是陆地,就使用dfs把相邻的变成海洋
        for(int i=0;i<grid.length;i++)
        {
            if(grid[i][0]==1)
                BFS(grid,i,0);
            if(grid[i][grid[0].length-1]==1)
                BFS(grid,i,grid[0].length-1);
        }

        for(int j=1;j<grid[0].length-1;j++)
        {
            if(grid[0][j]==1)
                BFS(grid,0,j);
            if(grid[grid.length-1][j]==1)
                BFS(grid,grid.length-1,j);
        }
        //计算陆地单元格的数量
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(grid[i][j]==1)
                    count++;
            }
        }
        return count;


    }
}

130、被围绕的区域

给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

示例 1:

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

被围绕的区域就是出不去的区域。

与上一题的思路类似,任何边界上的O以及与边界O相邻的O都不会变动,其他的O都会被填充为X,因此先对边界进行遍历,把这些与边界O以及边界相邻的O都标记为A ,然后遍历整个数组,把O变为X,把A变为O

DFS:

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    public void DFS(char[][] board,int x,int y)
    {
        if(board[x][y]=='X'||board[x][y]=='A')
            return;
        //把边界O以及边界相邻的O标记为A
        board[x][y]='A';
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=board.length||ny>=board[0].length)
                continue;
            DFS(board,nx,ny);
        }
    }

    public void solve(char[][] board) {

        //对边界进行DFS
        for(int i=0;i<board.length;i++)
        {
            if(board[i][0]=='O')
                DFS(board,i,0);
            if(board[i][board[0].length-1]=='O')
                DFS(board,i,board[0].length-1);
        }
        for(int j=1;j<board[0].length-1;j++)
        {
            if(board[0][j]=='O')
                DFS(board,0,j);
            if(board[board.length-1][j]=='O')
                DFS(board,board.length-1,j);
        }
        for(int i=0;i<board.length;i++)
        {
            for(int j=0;j<board[0].length;j++)
            {
                if(board[i][j]=='O')
                    board[i][j]='X';
                if(board[i][j]=='A')   //注意这两个的if顺序不能颠倒
                    board[i][j]='O';
            }
        }
    }
}

BFS:

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    public void BFS(char[][] board,int x,int y)
    {
        Queue<int[]> queue =new LinkedList<>();
        queue.offer(new int[]{x,y});
        //先把O转换成A
        board[x][y]='A';
        while(!queue.isEmpty())
        {
            int[] cur=queue.poll();
            int cx=cur[0];
            int cy=cur[1];
            for(int i=0;i<4;i++)
            {
                int nx=cx+dir[i][0];
                int ny=cy+dir[i][1];
                if(nx<0||ny<0||nx>=board.length||ny>=board[0].length)
                    continue;
                if(board[nx][ny]=='X' ||board[nx][ny]=='A')
                    continue;
                queue.offer(new int[]{nx,ny});
                board[nx][ny]='A';   //把O入队并修改成A

            }
        }

    }

    public void solve(char[][] board) {

        //对边界进行BFS
        for(int i=0;i<board.length;i++)
        {
            if(board[i][0]=='O')
                BFS(board,i,0);
            if(board[i][board[0].length-1]=='O')
                BFS(board,i,board[0].length-1);
        }
        for(int j=1;j<board[0].length-1;j++)
        {
            if(board[0][j]=='O')
                BFS(board,0,j);
            if(board[board.length-1][j]=='O')
                BFS(board,board.length-1,j);
        }
        for(int i=0;i<board.length;i++)
        {
            for(int j=0;j<board[0].length;j++)
            {
                if(board[i][j]=='O')
                    board[i][j]='X';
                if(board[i][j]=='A')   //注意这两个的if顺序不能颠倒
                    board[i][j]='O';
            }
        }
    }
}

417、太平洋大西洋水流问题

有一个 m × n 的矩形岛屿,与 太平洋大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。

这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度

岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。

返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋

示例 1:

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

思路:与之前类似,本题只需要设置两个数组来分别记录能流入太平洋和流入大西洋的位置,然后就可以找到既可流向太平洋也可流向大西洋的位置,因为海洋是在周边,所以还是要从周边进行遍历。标记能流入到周边的

DFS:

class Solution {
    int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};
    //使用DFS来找到能到达海洋的位置
    public void DFS(int[][] heights,boolean[][] mark,int x,int y)
    {
        if(mark[x][y]==true)
            return;
        mark[x][y]=true;
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=heights.length||ny>=heights[0].length)
                continue;
            if(heights[x][y]>heights[nx][ny])  //能流入到周边的
                continue;
            DFS(heights,mark,nx,ny);	
        }
    }

    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        List<List<Integer>> ans =new ArrayList<>();
        int m=heights.length;
        int n=heights[0].length;
        boolean[][] pacific=new boolean[m][n];    //标记能流入太平洋的位置
        boolean[][] atlantic=new boolean[m][n];   //标记能流入大西洋的位置
        for(int i=0;i<m;i++)
        {
            DFS(heights,pacific,i,0); //遍历最左列
            DFS(heights,atlantic,i,n-1);  //遍历最右列

        }
        for(int j=0;j<n;j++)//这个循环要从0开始,因为和大西洋也是相邻的
        {
            DFS(heights,pacific,0,j);   //最上列
            DFS(heights,atlantic,m-1,j); //最下列
        }

        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(pacific[i][j]==true && atlantic[i][j]==true)
                    ans.add(List.of(i,j));

            }
        }

        return ans;


    }
}

BFS:

class Solution {
    int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};
    //使用BFS来找到能到达海洋的位置
    public void BFS(int[][] heights,boolean[][] mark,int x,int y)
    {
        Queue<int[]> queue =new LinkedList<>();
        queue.offer(new int[]{x,y});
        mark[x][y]=true;
        while(!queue.isEmpty())
        {
            int[] cur=queue.poll();
            int cx=cur[0];
            int cy=cur[1];
            for(int i=0;i<4;i++)
            {
                int nx=cx+dir[i][0];
                int ny=cy+dir[i][1];
                if(nx<0||ny<0||nx>=heights.length||ny>=heights[0].length)
                    continue;
                //高度不合适或者被访问过了
                //一定要加mark[nx][ny]==true,否则会死循环
                if(heights[cx][cy]>heights[nx][ny] ||mark[nx][ny]==true)
                    continue;
                queue.offer(new int[]{nx,ny});  //能够到达海洋
                mark[nx][ny]=true;
            }
        }

    }

    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        List<List<Integer>> ans =new ArrayList<>();
        int m=heights.length;
        int n=heights[0].length;
        boolean[][] pacific=new boolean[m][n];    //标记能流入太平洋的位置
        boolean[][] atlantic=new boolean[m][n];   //标记能流入大西洋的位置
        for(int i=0;i<m;i++)
        {
            //可以共用一个队列,因为执行完一个BFS后,队列就空了
            BFS(heights,pacific,i,0); //遍历最左列
            BFS(heights,atlantic,i,n-1);  //遍历最右列

        }
        for(int j=0;j<n;j++)//这个循环要从0开始,因为和大西洋也是相邻的
        {
            BFS(heights,pacific,0,j);   //最上列
            BFS(heights,atlantic,m-1,j); //最下列
        }

        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(pacific[i][j]==true && atlantic[i][j]==true)
                    ans.add(List.of(i,j));

            }
        }

        return ans;


    }
}

827、最大人工岛

给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。

返回执行此操作后,grid 中最大的岛屿面积是多少?

岛屿 由一组上、下、左、右四个方向相连的 1 形成。

示例 1:

输入: grid = [[1, 0], [0, 1]]
输出: 3
解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。

示例 2:

输入: grid = [[1, 1], [1, 0]]
输出: 4
解释: 将一格0变成1,岛屿的面积扩大为 4。

示例 3:

输入: grid = [[1, 1], [1, 1]]
输出: 4
解释: 没有0可以让我们变成1,面积依然为 4。

暴力思路:

遍历地图尝试 将每一个 0 改成1,然后去使用DFS或者BFS搜索地图中的最大的岛屿面积,整体时间复杂度为:n^4。

优化思路:

其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。

只要用一次深搜把每个岛屿的面积记录下来就好。

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积

第二步:在遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。

拿如下地图的岛屿情况来举例: (1为陆地)

第一步,则遍历题目,并将岛屿到编号和面积上的统计,过程如图所示:

本过程代码如下:

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    int count=0;
    boolean[][] visited;  //访问数组

    //使用DFS把相邻的陆地都标记成一样的数字
    public void DFS(int[][] grid,int x,int y,int mark)
    {
        if(visited[x][y]||grid[x][y]==0)
            return;
        visited[x][y]=true; //标记访问过
        grid[x][y]=mark;   //给陆地添加新标记
        count++;
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                continue;
            DFS(grid,nx,ny,mark);

        }
    }
    public int largestIsland(int[][] grid) {
        int n=grid.length;
        int m=grid[0].length;
        visited=new boolean[n][m];
        int[] gridNum=new int[n*m]; //记录每一个岛屿的面积
        int mark=2;
        boolean isAllGrid = true; // 标记是否整个地图都是陆地
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]==0) 
                    isAllGrid=false;
                if(grid[i][j]==1 &&visited[i][j]==false)
                {
                    count=0;   //每次DFS之前把count清空
                    DFS(grid,i,j,mark);  //使用DFS把相邻的陆地都标记成一样的数字
                    gridNum[mark]=count;
                    mark++;

                }
            }
        }

    }
}

这个过程的时间复杂度是n²,因为n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历

第二步过程如图所示

也就是遍历每一个0的方格,并统计其相邻岛屿面积,最后取一个最大值。这个过程的时间复杂度也为 n²。

所以整个代码的复杂度就是n²

class Solution {
    int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
    int count=0;
    boolean[][] visited;  //访问数组

    //使用DFS把相邻的陆地都标记成一样的数字
    public void DFS(int[][] grid,int x,int y,int mark)
    {
        if(visited[x][y]||grid[x][y]==0)
            return;
        visited[x][y]=true; //标记访问过
        grid[x][y]=mark;   //给陆地添加新标记
        count++;
        for(int i=0;i<4;i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                continue;
            DFS(grid,nx,ny,mark);

        }
    }
    public int largestIsland(int[][] grid) {
        int n=grid.length;
        int m=grid[0].length;
        visited=new boolean[n][m];
        int[] gridNum=new int[n*m+2]; //记录每一个岛屿的面积,加2是防止矩阵只有1的长度
        int mark=2;
        boolean isAllGrid = true; // 标记是否整个地图都是陆地
        //把相连的岛屿进行标记
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]==0) 
                    isAllGrid=false;
                if(grid[i][j]==1 &&visited[i][j]==false)
                {
                    count=0;   //每次DFS之前把count清空
                    DFS(grid,i,j,mark);  //使用DFS把相邻的陆地都标记成一样的数字
                    gridNum[mark]=count;
                    mark++;

                }
            }
        }
        //把一个0变为陆地,并计算最大的面积
        if(isAllGrid)
            return n*m;
        int result=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                if(grid[i][j]==0)   //把0换成1
                {
                    int num=1;   //0本身的面积
                    boolean[] visitedGrid =new boolean[mark]; //每次变过之后,标记0周边访问过的岛屿,有可能相邻的是属于同一个岛屿
                    for(int k=0;k<4;k++)
                    {
                        int nx=i+dir[k][0];
                        int ny=j+dir[k][1];
                        if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                            continue;
                        if(visitedGrid[grid[nx][ny]]==true)
                            continue;
                        num+=gridNum[grid[nx][ny]];  // 把相邻四面的岛屿数量加起来
                        visitedGrid[grid[nx][ny]]=true;  // 标记该岛屿已经添加过
                    }
                    result=Math.max(result,num);
                }

            }
        }
        return result;
    }
}

把问题转换为图的遍历

127、单词接龙

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> … -> sk:

  • 每一对相邻的单词只差一个字母。
  • 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 beginWordendWord最短转换序列 中的 __单词数目 。如果不存在这样的转换序列,返回 0 。

示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

这道题要解决两个问题:

  • 图中的线是如何连在一起的
  • 起点和终点的最短路径长度

首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接

然后就是求起点和终点的最短路径长度,这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。

另外需要有一个注意点:

  • 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
  • 本题给出集合是数组型的,可以转成set结构,查找更快一些

转换为图求最短路径

使用一个队列记录bfs的结点,并使用一个map记录访问过的结点和路径长度,使用Hashset判断更换后的单词是否在列表中。

class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        HashSet<String> wordset =new HashSet<>(wordList); //加快判断速度
        if(wordset.size()==0 ||!wordset.contains(endWord))
            return 0;
        Queue<String> queue =new LinkedList<>();
        queue.offer(beginWord);
        //记录单词对应的路径长度,同时也有标记功能,标记wordList的访问过的单词
        Map<String,Integer> map =new HashMap<>(); 
        map.put(beginWord,1);
        while(!queue.isEmpty())
        {
            String word=queue.poll(); //从队列中取出第一个单词
            int path=map.get(word);   //单词对应的路径长度
            for(int i=0;i<word.length();i++)   //遍历单词每个字符,把单词进行替换
            {
                char[] chars=word.toCharArray(); //变为数组,方便操作
                for(char k='a';k<='z';k++)
                {
                    chars[i]=k;
                    String newword=String.valueOf(chars); //替换成新单词
                    if(endWord.equals(newword))
                        return path+1;
                    //如果新单词在列表中,并且没有访问过
                    if(wordset.contains(newword) && !map.containsKey(newword))
                    {
                        queue.offer(newword); //加入队列
                        map.put(newword,path+1); //记录单词对应的路径长度

                    }
                }

            }
        } 
        return 0;//未找到
    }
}

841、钥匙和房间

有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。

当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。

给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。

示例 1:

输入:rooms = [[1],[2],[3],[]]
输出:true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。

示例 2:

输入:rooms = [[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。

相当于一个有向图,rooms[i] 就是i结点能到达的结点。能到达就标记,然后看最后能否标记完

DFS

class Solution {
    boolean[] visited;
    //DFS的第一种写法,处理当前访问的结点
    public void DFS(List<List<Integer>> rooms,int curKey)
    {
        if(visited[curKey])
            return;
        visited[curKey]=true;
        for(int key:rooms.get(curKey))
        {
            DFS(rooms,key);
        }
    }
    //DFS的第二种写法,处理下一个结点
    public void DFS(List<List<Integer>> rooms,int curKey)
    {
        //这里 没有终止条件,而是在 处理下一层节点的时候来判断
        for(int key:rooms.get(curKey))
        {
            if (visited[key] == false) { // 处理下一层节点,判断是否要进行递归
                {   visited[key] = true;
                 DFS(rooms,key);
                }
            }
        }


        public boolean canVisitAllRooms(List<List<Integer>> rooms) {
            visited=new boolean[rooms.size()]; //标记数组
            DFS(rooms,0);
            for(int i=0;i<visited.length;i++)
            {
                if(visited[i]==false)
                    return false;
            }
            return true;

        }
    }

什么本题就没有回溯呢?

代码中可以看到dfs函数下面并没有回溯的操作。

此时就要在思考本题的要求了,本题是需要判断 0节点是否能到所有节点,那么我们就没有必要回溯去撤销操作了,只要遍历过的节点一律都标记上。

那什么时候需要回溯操作呢?

当我们需要搜索一条可行路径的时候,就需要回溯操作了,因为没有回溯,就没法“调头”

BFS

class Solution {
    //因为只需要遍历一次BFS 所以可以写在主函数里
    public boolean canVisitAllRooms(List<List<Integer>> rooms) {
        boolean[] visited=new boolean[rooms.size()]; //标记数组
        Queue<Integer> queue =new LinkedList<>();
        queue.add(0);
        visited[0]=true;
        while(!queue.isEmpty())
        {
            int curKey=queue.poll();
            for(int key:rooms.get(curKey))
            {
                if(visited[key]==false)
                {
                    queue.add(key);
                    visited[key]=true;
                }
            }
        }
        for(int i=0;i<visited.length;i++)
        {
            if(visited[i]==false)
                return false;
        }
        return true;

    }
}

463、岛屿的周长

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。

网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

示例 1:

输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边

思路:

岛屿问题最容易让人想到BFS或者DFS,但是这道题还真的没有必要,别把简单问题搞复杂了

遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了

class Solution {
    public int islandPerimeter(int[][] grid) {
        int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};
        int count=0;
        for(int i=0;i<grid.length;i++)
        {
            for(int j=0;j<grid[0].length;j++)
            {
                if(grid[i][j]==1)  //遇到陆地
                {
                    for(int k=0;k<4;k++)
                    {
                        int nx=i+dir[k][0];
                        int ny=j+dir[k][1];
                        //新位置遇到边界,不要忘记continue,因为下面会利用到这些坐标
                        if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)
                        {
                            count++;
                            continue;
                        }
                        if(grid[nx][ny]==0)  //x新位置遇到水域,周长加1
                            count++;
                    }
                }
            }
        }
        return count;

    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JAVA基础:IO流 (学习笔记)

IO流 一&#xff0c;IO流的理解 i &#xff1a; input 输入 o&#xff1a;output 输入 流&#xff1a;方式&#xff0c;传递数据的方式---------相当于生活中的“管道”&#xff0c;“车”&#xff0c;将资源从一个位置&#xff0c;传递到另一个位置 二&#xff0c;IO流的分…

从0开始深度学习(16)——暂退法(Dropout)

上一章的过拟合是由于数据不足导致的&#xff0c;但如果我们有比特征多得多的样本&#xff0c;深度神经网络也有可能过拟合 1 扰动的稳健性 经典泛化理论认为&#xff0c;为了缩小训练和测试性能之间的差距&#xff0c;应该以简单的模型为目标&#xff0c;即模型以较小的维度的…

Qt中使用线程之QConcurrent

QConcurrent可以实现并发&#xff0c;好处是我们可以不用单独写一个类了&#xff0c;直接在类里面定义任务函数&#xff0c;然后使用QtConcurrent::run在单独的线程里执行一个任务 1、定义一个任务函数 2、定义1个QFutureWatcher的对象&#xff0c;使用QFutureWatcher来监测任…

新手直播方案

简介 新手直播方案 &#xff0c;低成本方案 手机/电脑 直接直播手机软件电脑直播手机采集卡麦电脑直播多摄像机 机位多路采集卡 多路麦加电脑&#xff08;高成本方案&#xff09; 直播推流方案 需要摄像头 方案一 &#xff1a;手机 电脑同步下载 网络摄像头 软件&#xff08…

【学术论文投稿】Windows11开发指南:打造卓越应用的必备攻略

【IEEE出版南方科技大学】第十一届电气工程与自动化国际会议&#xff08;IFEEA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看&#xff1a;https://ais.cn/u/nuyAF3 目录 引言 一、Windows11开发环境搭建 二、Windows11关键新特性 三、Windows11设计指南 …

小程序开发实战:PDF转换为图片工具开发

目录 一、开发思路 1.1 申请微信小程序 1.2 编写后端接口 1.3 后端接口部署 1.4 微信小程序前端页面开发 1.5 运行效果 1.6 小程序部署上线 今天给大家分享小程序开发系列&#xff0c;PDF转换为图片工具的开发实战&#xff0c;感兴趣的朋友可以一起来学习一下&#xff01…

linux中级(NFS服务器)

NFS&#xff1a;用于在NNIX/Linux主机之间进行文件共享的协议 流程&#xff1a;首先服务端开启RPC服务&#xff0c;并开启111端口&#xff0c;服务器端启动NFS服务&#xff0c;并向RPC注册端口信息&#xff0c;客户端启动RPC&#xff0c;向服务器RPC服务请求NFS端口&#xff0…

anaconda 创建环境失败 解决指南

anaconda 创建环境失败 解决指南 一、问题描述 我在宿舍有一台电脑。由于我经常泡在实验室&#xff0c;所以那台电脑不是经常用&#xff0c;基本吃灰。昨天晚上突然有在那台电脑上使用Camel-AI部署多智能体协同需求&#xff0c;便戳开了电脑&#xff0c;问题也随之而来。 当…

汽车级DC-DC转换器英飞凌TLF35584

上汽荣威都在用的汽车级DC-DC转换器英飞凌TLF35584 今天平台君从IPBrain数据库中给大家带来的一款由Infineon(英飞凌)推出的一款多路输出安全电源芯片,具备高可靠性和安全性。适用于汽车电子系统中的多种应用场景,如车身控制、安全气囊、防抱死制动系统,电子稳定控制系统等。…

设计模式基础知识以及典型设计模式总结(上)

1. 基础 1. 什么是设计模式&#xff1f; 设计模式是指在软件开发中&#xff0c;经过验证的&#xff0c;用于解决在特定环境下重复出现的特定问题的解决方案。 简单的说&#xff0c;设计模式是解决问题的固定套路。 慎用设计模式。 2. 设计模式是怎么来的&#xff1f; 满足…

Unity3D学习FPS游戏(4)重力模拟和角色跳跃

前言&#xff1a;前面两篇文章&#xff0c;已经实现了角色的移动和视角转动&#xff0c;但是角色并没有办法跳跃&#xff0c;有时候还会随着视角移动跑到天上。这是因为缺少重力系统&#xff0c;本篇将实现重力和角色跳跃功能。觉得有帮助的话可以点赞收藏支持一下&#xff01;…

fmql之Linux RTC

模拟i2c&#xff0c;连接rtc芯片。 dts&#xff1a; /{ // 根节点i2c_gpio: i2c-gpio {#address-cells <1>;#size-cells <0>;compatible "i2c-gpio";// MIO56-SDA, MIO55-SCL // 引脚编号gpios <&portc 2 0&portc 1 0 >;i2c-gp…

Unity3D学习FPS游戏(2)简单场景、玩家移动控制

前言&#xff1a;上一篇的时候&#xff0c;我们已经导入了官方fps的素材&#xff0c;并且对三维模型有了一定了解。接下来我们要构建一个简单的场景让玩家能够有地方移动&#xff0c;然后写一个简单的玩家移动控制。 简单场景和玩家移动 简单场景玩家移动控制玩家模型视野-摄像…

单细胞配色效果模拟器 | 简陋版(已有颜色数组)

目的&#xff1a;假设你有一组颜色了&#xff0c;怎么模拟查看它们在单细胞DimPlot中的美学效果呢&#xff1f;要足够快&#xff0c;还要尽可能有模拟效果。 1. 尝试1: 随机矩阵&#xff0c;真的UMAP降维后绘图&#xff08;失败&#xff09; 造一个随机矩阵&#xff0c;使用S…

华为鸿蒙HarmonyOS应用开发者高级认证视频及题库答案

华为鸿蒙开发者高级认证的学习资料 1、课程内容涵盖HarmonyOS系统介绍、DevEco Studio工具使用、UI设计与开发、Ability设计与开发、分布式特性、原子化服务卡片以及应用发布等。每个实验都与课程相匹配&#xff0c;帮助加深理解并掌握技能 2、学习视频资料 华为HarmonyOS开发…

全能大模型GPT-4o体验和接入教程

GPT-4o体验和接入教程 前言一、原生API二、Python LangchainSpring AI总结 前言 Open AI发布了产品GPT-4o&#xff0c;o表示"omni"&#xff0c;全能的意思。 GPT-4o可以实时对音频、视觉和文本进行推理&#xff0c;响应时间平均为 320 毫秒&#xff0c;和人类之间对…

动态规划 —— 斐波那契数列模型-解码方法

1. 解码方法 题目链接&#xff1a; 91. 解码方法 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/decode-ways/description/ 2. 题目解析 1. 对字母A - Z进行编码1-26 2. 11106可以解码为1-1-10-6或者11-10-6, 但是11-1-06不能解码 3. 0n不能解码 4. …

微知-Lecroy力科的PCIe协议分析仪型号命名规则(PCIe代,金手指lanes数量)

文章目录 要点主要型号命名规则各代主要产品图片Summit M616 协议分析仪/训练器Summit T516 分析仪Summit T416 分析仪Summit T3-16分析仪Summit T28 分析仪 综述 要点 LeCroy(力科)成立于1964年&#xff0c;是一家专业生产示波器厂家。在美国纽约。一直把重点放在研制改善生产…

【Linux】线程池详解及其基本架构与单例模式实现

目录 1.关于线程池的基本理论 1.1.线程池是什么&#xff1f; 1.2.线程池的应用场景&#xff1a; 2.线程池的基本架构 2.1.线程容器 2.2.任务队列 2.3.线程函数&#xff08;HandlerTask&#xff09; 2.4.线程唤醒机制 3.添加单例模式 3.1.单例模式是什么&…

【Canvas与图标】六色彩虹圆角六边形图标

【成图】 120*120的png图标 以下是各种大小图&#xff1a; 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>六色彩虹圆角六边形…