代码随想录 图论

news2024/12/28 20:24:10

目录

797.所有可能得路径 

200.岛屿数量

695.岛屿的最大面积

1020.飞地的数量 

130.被围绕的区域 

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

827.最大人工岛

127.单词接龙 

841.钥匙和房间

463.岛屿的周长 


797.所有可能得路径 

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

示例 2:

输入:graph = [[4,3,1],[3,2,4],[3],[4],[]]
输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]

提示:

  • n == graph.length
  • 2 <= n <= 15
  • 0 <= graph[i][j] < n
  • graph[i][j] != i(即不存在自环)
  • graph[i] 中的所有元素 互不相同
  • 保证输入为 有向无环图(DAG)

 dfs + 回溯

class Solution {
    List<List<Integer>> result; // 存储所有路径的结果集
    List<Integer> path; // 存储当前路径的列表

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        result = new ArrayList<>(); // 初始化结果集
        path = new ArrayList<>(); // 初始化当前路径

        path.add(0); // 将起始节点添加到当前路径中
        dfs(graph, 0); // 开始深度优先搜索

        return result; // 返回结果集
    }

    public void dfs(int[][] graph, int node) {
        if (node == graph.length - 1) { // 如果当前节点是目标节点
            result.add(new ArrayList<>(path)); // 将当前路径添加到结果集中
            return; // 返回
        }

        // 遍历当前节点的邻居节点
        for (int i = 0; i < graph[node].length; i++) {
            int nextNode = graph[node][i]; // 获取下一个节点
            path.add(nextNode); // 将下一个节点添加到当前路径中
            dfs(graph, nextNode); // 递归搜索以下一个节点为起点的路径
            path.remove(path.size() - 1); // 回溯,移除当前路径中的最后一个节点
        }
    }
}

200.岛屿数量

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

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0' 或 '1'

dfs 

逐个遍历元素,遍历到为1的元素岛屿数量就+1,通过dfs向上下左右方向的位置dfs搜索,将该岛屿首个元素能辐射到的范围中的为1的陆地全部修改为‘0’,这样该元素在再次被访问到的时候就不会计数或者向四周访问(因为每个位置向上下左右访问,访问道德位置又向上下左右访问,这样会一直访问,导致栈溢出)。

或者使用Boolean+‘0’数组来记录访问状态是一样的

class Solution {
    
    public int numIslands(char[][] grid) {
        int result = 0; // 记录岛屿数量
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1') { // 如果当前位置是陆地
                    result++; // 岛屿数量加一
                    dfs(grid, i, j); // 对当前岛屿进行深度优先搜索
                }
            }
        }
        return result; // 返回岛屿数量
    }
    
    // 深度优先搜索函数,用于将当前岛屿所有相连的陆地转换为水域
    public void dfs(char[][] grid, int i, int j) {
        // 边界条件检查:索引越界或当前位置是水域
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') {
            return;
        }
        // 将当前位置标记为水域
        grid[i][j] = '0';
        // 对当前位置的上下左右四个相邻位置进行深度优先搜索
        dfs(grid, i - 1, j); // 上
        dfs(grid, i + 1, j); // 下
        dfs(grid, i, j - 1); // 左
        dfs(grid, i, j + 1); // 右
    }
}
class Solution {
    boolean[][] visited; // 记录访问状态的二维数组
    int dir[][] = {
        {0, 1},  // 右
        {1, 0},  // 下
        {-1, 0}, // 上
        {0, -1}  // 左
    };

    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; // 返回岛屿数量
    }

    // 深度优先搜索函数,用于访问当前岛屿上的所有陆地
    private void dfs(char[][] grid, int x, int y) {
        // 边界条件检查:索引越界、当前位置已访问、当前位置为水域
        if(x < 0 || y < 0 || x >= grid.length || y >= grid[0].length || visited[x][y] == true || grid[x][y] == '0') {
            return;
        } 

        visited[x][y] = true; // 标记当前位置为已访问

        // 遍历四个方向
        for(int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0]; // 下一个位置的行索引
            int nextY = y + dir[i][1]; // 下一个位置的列索引
            dfs(grid, nextX, nextY); // 递归访问下一个位置
        }
        // 对当前位置的上下左右四个相邻位置进行深度优先搜索,遍历或者这种方式都可以
        //dfs(grid, x - 1, y); // 上
        //dfs(grid, x + 1, y); // 下
        //dfs(grid, x, y - 1); // 左
        //dfs(grid, x, y + 1); // 右
    }
}

广度优先遍历,遇到‘1’的情况就将岛屿数量+1, 并广度优先遍历上下左右的元素,遇到‘1’就标记已经访问过,遇到‘0’就停止向下访问。

或者使用标记为‘0’的方式来标记访问过也是可以的。

class Solution {

    boolean[][] visited; // 记录已访问的位置
    int[][] move = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // 上下左右四个方向的移动数组

    public int numIslands(char[][] grid) {
        int res = 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] && grid[i][j] == '1') { // 如果当前位置是未访问过的陆地
                    bfs(grid, i, j); // 进行广度优先搜索,将当前岛屿所有陆地访问到
                    res++; // 岛屿数量加一
                }
            }
        }
        return res; // 返回岛屿数量
    }

    // 广度优先搜索函数,用于将当前岛屿上的所有陆地都访问到
    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 m = cur[0]; // 当前位置的行坐标
            int n = cur[1]; // 当前位置的列坐标
            for(int i = 0; i < 4; i++) { // 遍历四个方向
                int nextx = m + move[i][0]; // 计算下一个位置的行坐标
                int nexty = n + move[i][1]; // 计算下一个位置的列坐标
                // 检查下一个位置是否越界或已访问,如果没有则将其加入队列并标记为已访问
                if(nextx < 0 || nextx == grid.length || nexty < 0 || nexty == grid[0].length || visited[nextx][nexty] || grid[nextx][nexty] == '0') continue;
                queue.offer(new int[]{nextx, nexty}); // 将下一个位置加入队列
                visited[nextx][nexty] = true; // 标记下一个位置为已访问
            }
        }
    }
}
class Solution {

    int[][] move = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // 上下左右四个方向的移动数组

    public int numIslands(char[][] grid) {
        int res = 0; // 记录岛屿数量
        for(int i = 0; i < grid.length; i++) {
            for(int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1') { // 如果当前位置是未访问过的陆地
                    bfs(grid, i, j); // 进行广度优先搜索,将当前岛屿所有陆地访问到
                    res++; // 岛屿数量加一
                }
            }
        }
        return res; // 返回岛屿数量
    }

    // 广度优先搜索函数,用于将当前岛屿上的所有陆地都访问到
    public void bfs(char[][] grid, int x, int y) {
        Deque<int[]> queue = new ArrayDeque<>(); // 使用双端队列保存待访问的陆地位置
        queue.offer(new int[]{x, y}); // 将当前位置加入队列
        grid[x][y] = '0'; // 标记当前位置为已访问
        while(!queue.isEmpty()) { // 当队列不为空时循环
            int[] cur = queue.poll(); // 弹出队首位置
            int m = cur[0]; // 当前位置的行坐标
            int n = cur[1]; // 当前位置的列坐标
            for(int i = 0; i < 4; i++) { // 遍历四个方向
                int nextx = m + move[i][0]; // 计算下一个位置的行坐标
                int nexty = n + move[i][1]; // 计算下一个位置的列坐标
                // 检查下一个位置是否越界或已访问,如果没有则将其加入队列并标记为已访问
                if(nextx < 0 || nextx == grid.length || nexty < 0 || nexty == grid[0].length || grid[nextx][nexty] == '0') continue;
                queue.offer(new int[]{nextx, nexty}); // 将下一个位置加入队列
                grid[nextx][nexty] = '0'; // 标记下一个位置为已访问
            }
        }
    }
}

695.岛屿的最大面积

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

示例 2:

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

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] 为 0 或 1

 感觉还是用dfs+赋零淹没这样的思路比较简单,设定一个curArea值储存当前海岛的面积,dfs来计算面积,计算完毕后重新赋值result(最大海岛面积),处理完这个海岛后重置当前岛屿面积

class Solution {
    int result = 0; // 存储最大岛屿面积的变量
    int curArea = 0; // 存储当前岛屿的面积的变量

    // 计算最大岛屿面积的方法
    public int maxAreaOfIsland(int[][] grid) {
        // 遍历整个网格
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                // 如果当前位置是陆地(值为1)
                if(grid[i][j] == 1){
                    // 进行深度优先搜索
                    dfs(grid,i,j);
                    // 更新最大岛屿面积
                    result = Math.max(result,curArea);
                    // 重置当前岛屿面积
                    curArea = 0;
                }
            }
        }
        // 返回最大岛屿面积
        return result;
    }

    // 深度优先搜索方法
    public void dfs(int[][] grid, int i, int j){
        // 如果当前位置超出了网格范围或者当前位置是水(值为0),则返回
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0){
            return;
        }
        // 将当前位置标记为已访问(水)
        grid[i][j] = 0;
        // 当前岛屿面积加一
        curArea++;
        // 继续向四个方向进行深度优先搜索
        dfs(grid,i - 1, j);
        dfs(grid,i, j + 1);
        dfs(grid,i, j - 1);
        dfs(grid,i + 1, j);
    }
}

1020.飞地的数量 

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 都在边界上或可以到达边界。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 500
  • grid[i][j] 的值为 0 或 1

从该网格的边界开始遍历,因为如果一个位置不是飞地,那他不断向上下左右访问就会访问到边界的陆地,同样通过边界的陆地也可以访问到这个不是飞地的位置,继而将这些位置的值都设为0,下次不会访问,同时防止重复访问导致栈溢出,这样操作完后剩下的标记为1的地方都是飞地。 

class Solution {
    // 计算被海洋包围的陆地数量的方法
    public int numEnclaves(int[][] grid) {
        int n = grid.length; // 获取网格的行数
        int m = grid[0].length; // 获取网格的列数

        // 从上下两侧开始,将与边界相连的陆地标记为已访问
        for (int i = 0; i < n; i++) {
            dfs(grid, i, 0); // 从第一列开始深度优先搜索
            dfs(grid, i, m - 1); // 从最后一列开始深度优先搜索
        }
        
        // 从左右两侧开始,将与边界相连的陆地标记为已访问
        for (int j = 0; j < m; j++) {
            dfs(grid, 0, j); // 从第一行开始深度优先搜索
            dfs(grid, n - 1, j); // 从最后一行开始深度优先搜索
        }

        int res = 0; // 初始化结果变量,用于计算未被海洋包围的陆地数量
        // 统计未被标记为已访问的陆地数量
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == 1) { // 如果该位置为陆地
                    res++; // 未被海洋包围的陆地数量加一
                }
            }
        }
        return res; // 返回未被海洋包围的陆地数量
    }

    // 深度优先搜索函数,用于标记与当前位置相连的陆地
    void dfs(int[][] grid, int i, int j) {
        int n = grid.length; // 获取网格的行数
        int m = grid[0].length; // 获取网格的列数

        // 如果当前位置超出了网格边界,或者当前位置为海洋,直接返回
        if (i < 0 || i >= n || j < 0 || j >= m || grid[i][j] == 0) {
            return;
        }

        grid[i][j] = 0; // 将当前位置标记为已访问

        // 对当前位置的上下左右四个方向进行深度优先搜索
        dfs(grid, i + 1, j); // 向下搜索
        dfs(grid, i - 1, j); // 向上搜索
        dfs(grid, i, j - 1); // 向左搜索
        dfs(grid, i, j + 1); // 向右搜索
    }
}

笨办法,我自己的思路,去访问每一个位置,如果能到达网格外,就代表不是飞地,这里为了避免重复访问使用了一个visited数组,每个位置访问的时候都要新建一个。或者说每次访问都去复制一个grip数组,然后通过赋值为0的方式去避免重复访问。

import java.util.Arrays;

class Solution {
    boolean[][] visited; // 用于记录已访问的位置
    
    public int numEnclaves(int[][] grid) {
        int result = 0;
        // 遍历二维网格
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                // 如果当前位置是陆地且未被访问过
                if(grid[i][j] == 1){
                    visited = new boolean[grid.length][grid[0].length];
                    // 进行深度优先搜索
                    if(!dfs(grid,i,j)){
                        result++;
                    }
                }
            }
        }
        return result;
    }
    
    // 深度优先搜索方法
    public boolean dfs(int[][] grid, int i, int j){
        // 判断当前位置是否超出网格范围
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length){
            return true;
        }
        // 如果当前位置是海洋或已被访问过,则返回false
        if(grid[i][j] == 0 || visited[i][j]){
            return false;
        }
        // 将当前位置标记为已访问
        visited[i][j] = true; // 将当前陆地标记为已访问
        // 递归地向四个方向进行深度优先搜索
        boolean up = dfs(grid,i - 1,j);
        boolean down = dfs(grid,i + 1,j);
        boolean left = dfs(grid,i,j - 1);
        boolean right = dfs(grid,i,j + 1);
        // 如果上下左右任意一个方向上有陆地,则当前陆地不会被包围
        return up || down || left || right;
    }
}

130.被围绕的区域 

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'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

示例 2:

输入:board = [["X"]]
输出:[["X"]]

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] 为 'X' 或 'O'

先遍历网格边缘上的位置,向四周遍历,能遍历到的位置就不是被x包围的位置,重新赋值为A为标记,遍历完网格边缘上的值后对整个网格进行遍历,遇到O就说明是被X包围的值,改为X,遇到X不变,遇到A改回为O。

class Solution {
    int m, n; // 定义行数和列数

    public void solve(char[][] board) {
        m = board.length; // 获取行数
        n = board[0].length; // 获取列数

        // 从四条边界开始进行深度优先搜索
        for (int i = 0; i < m; i++) {
            dfs(board, i, 0); // 从左边界开始搜索
            dfs(board, i, n - 1); // 从右边界开始搜索
        }

        for (int j = 0; j < n; j++) {
            dfs(board, 0, j); // 从上边界开始搜索
            dfs(board, m - 1, j); // 从下边界开始搜索
        }

        // 将未被标记的 'O' 修改为 'X',标记的 'A' 修改为 'O'
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'A') {
                    board[i][j] = 'O'; // 'A' 表示与边界相连的 'O'
                } else if (board[i][j] == 'O') {
                    board[i][j] = 'X'; // 'O' 表示未被包围的 'O'
                }
            }
        }
    }

    // 深度优先搜索函数
    void dfs(char[][] board, int i, int j) {
        // 判断边界条件和当前字符是否为 'O'
        if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O') {
            return; // 越界或已访问过的位置直接返回
        }

        board[i][j] = 'A'; // 标记当前位置为与边界相连的 'O'

        // 对当前位置的上、下、左、右四个方向进行递归搜索
        dfs(board, i - 1, j); // 上
        dfs(board, i + 1, j); // 下
        dfs(board, i, j - 1); // 左
        dfs(board, i, j + 1); // 右
    }
}

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

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]]

示例 2:

输入: heights = [[2,1],[1,2]]
输出: [[0,0],[0,1],[1,0],[1,1]]

提示:

  • m == heights.length
  • n == heights[r].length
  • 1 <= m, n <= 200
  • 0 <= heights[r][c] <= 105

这道题还是通过边界向内访问,使用了两个数组来标记大西洋和太平洋的到达情况,与前一个位置的高度比较我采用了两个参数来传递,最后遍历两个访问情况的数组,均为true则满足情况,放入结果数组集中,注意在访问判断大西洋到达情况和太平洋到达情况的时候需要分别有一个visited数组

class Solution {
    List<List<Integer>> result; // 用于存储结果的列表
    boolean[][] pacific; // 用于标记能流入太平洋的位置
    boolean[][] atlantic; // 用于标记能流入大西洋的位置
    boolean[][] visited; // 用于标记已访问过的位置
    
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        result = new ArrayList<>(); // 初始化结果列表
        pacific = new boolean[heights.length][heights[0].length]; // 初始化太平洋标记数组
        atlantic = new boolean[heights.length][heights[0].length]; // 初始化大西洋标记数组
        visited = new boolean[heights.length][heights[0].length]; // 初始化访问标记数组
        
        // 从太平洋边界开始进行深度优先搜索
        for(int i = 0; i < heights.length; i++){
            dfs(heights, i, 0, true, i, 0); // 从左边界开始搜索
        }
        for(int j = 0; j < heights[0].length; j++){
            dfs(heights, 0, j, true, 0, j); // 从上边界开始搜索
        }
        //因为一次是求大西洋的情况,一个是求太平洋的情况,需要两个visited数组
        visited = new boolean[heights.length][heights[0].length]; // 重置访问标记数组
        
        // 从大西洋边界开始进行深度优先搜索
        for(int i = 0; i < heights.length; i++){
            dfs(heights, i, heights[0].length - 1, false, i, heights[0].length - 1); // 从右边界开始搜索
        }
        for(int j = 0; j < heights[0].length; j++){
            dfs(heights, heights.length - 1, j, false, heights.length - 1, j); // 从下边界开始搜索
        }
        
        // 找到能同时流入太平洋和大西洋的位置
        for(int i = 0; i < heights.length; i++){
            for(int j = 0; j < heights[0].length; j++){
                if(pacific[i][j] && atlantic[i][j]){ // 如果当前位置能同时流入太平洋和大西洋
                    List<Integer> place = new ArrayList<>(); // 创建一个位置列表
                    place.add(i); // 添加当前位置的行索引
                    place.add(j); // 添加当前位置的列索引
                    result.add(place); // 将该位置添加到结果列表中
                }
            }
        }
        return result; // 返回结果列表
    }
    
    // 深度优先搜索函数
    public void dfs(int[][] heights, int i, int j, boolean choose, int prei, int prej){
        // 如果当前位置越界、高度小于前一个位置或已访问过,则返回
        if (i < 0 || i >= heights.length || j < 0 || j >= heights[0].length || heights[i][j] < heights[prei][prej] || visited[i][j]) {
            return; 
        }
        
        visited[i][j] = true; // 标记当前位置为已访问
        if(choose){
            pacific[i][j] = true; // 标记当前位置能流入太平洋
        } else {
            atlantic[i][j] = true; // 标记当前位置能流入大西洋
        }
        
        // 对当前位置的上、下、左、右四个方向进行递归搜索
        dfs(heights, i - 1, j, choose, i, j); // 上
        dfs(heights, i + 1, j, choose, i, j); // 下
        dfs(heights, i, j - 1, choose, i, j); // 左
        dfs(heights, i, j + 1, choose, i, j); // 右
    }
}

827.最大人工岛

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。

提示:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 500
  • grid[i][j] 为 0 或 1

最直观的方法是遍历数组,找到0的话就去访问周围的位置,找出将该0视为陆地的岛屿面积,然后找出最大的那一个。(我这里没有写)

优化方案:先找出每个岛屿的面积,并且将其标记存入Map数组中,修改该位置的值为mark值。(mark的值大于2,这样不会和原来的0和1冲突),完成后遍历整个数组,遇到0的情况向四周寻找一圈,即和它上下左右相邻的陆地,将该陆地所在的岛屿加入面积中(这里使用list来去重,防止该水域有两面或多面挨着的是一个岛屿),比较求出最大值

class Solution {
    Map<Integer,Integer> map; // 存储岛屿面积的映射
    int curArea = 0; // 存储当前岛屿的面积的变量
    boolean[][] visited; // 记录网格中的位置是否已经访问过
    int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 四个方向的偏移量

    // 计算最大岛屿面积的方法
    public int largestIsland(int[][] grid) {
        map = new HashMap<>(); // 初始化岛屿面积的映射
        int result = Integer.MIN_VALUE; // 存储最大岛屿面积的变量
        int mark = 2; // 标记每个岛屿
        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++){
                // 如果当前位置是陆地(值为1)
                if(grid[i][j] == 1){
                    // 进行深度优先搜索
                    dfs(grid,i,j,mark);
                    // 更新最大岛屿面积
                    map.put(mark++,curArea);
                    // 重置当前岛屿面积
                    curArea = 0;
                }
            }
        }
        
        // 计算每个水域周围的相邻岛屿面积,找出最大的岛屿面积
        int nowMaxArea = 1; // 当前水域的面积,初始化为1是因为将当前位置的0变为1
        List<Integer> list = new ArrayList<>(); // 记录周围相邻的岛屿的标记
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == 0){
                    // 遍历当前水域周围的相邻位置
                    for (int[] current: position) {
                        int curRow = i + current[0], curCol = j + current[1];
                        // 检查相邻位置是否越界
                        if (curRow < 0 || curRow >= grid.length || curCol < 0 || curCol >= grid[0].length){
                            continue;
                        } 
                        int curMark = grid[curRow][curCol]; // 获取相邻位置的标记
                        // 如果标记存在list中说明该标记被记录过一次,如果不存在map中说明该标记是无效标记(此时curMark=0)
                        if (list.contains(curMark) || !map.containsKey(curMark)){
                            continue;
                        } 
                        list.add(curMark); // 将相邻岛屿的标记加入list中
                        nowMaxArea += map.get(curMark); // 更新当前水域的面积
                    }
                    // 更新最大岛屿面积
                    result = Math.max(result,nowMaxArea);
                    // 重置当前水域面积
                    nowMaxArea = 1;
                    list.clear(); // 清空相邻岛屿的标记
                }
            }
        }
        // 返回最大岛屿面积,如果不存在岛屿则返回整个网格的面积
        return result == Integer.MIN_VALUE ? grid.length * grid[0].length : result;
    }

    // 深度优先搜索方法
    public void dfs(int[][] grid, int i, int j, int mark){
        // 如果当前位置超出了网格范围或者当前位置是水(值为0),或者已经访问过,则返回
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0 || visited[i][j]){
            return;
        }
        // 将当前位置标记为已访问(水)
        visited[i][j] = true;
        grid[i][j] = mark; // 标记当前位置属于的岛屿
        // 当前岛屿面积加一
        curArea++;
        // 继续向四个方向进行深度优先搜索
        dfs(grid,i - 1, j, mark); // 上
        dfs(grid,i, j + 1, mark); // 右
        dfs(grid,i, j - 1, mark); // 左
        dfs(grid,i + 1, j, mark); // 下
    }
}

127.单词接龙 

127. 单词接龙

困难

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

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

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 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" 不在字典中,所以无法进行转换。

提示:

  • 1 <= beginWord.length <= 10
  • endWord.length == beginWord.length
  • 1 <= wordList.length <= 5000
  • wordList[i].length == beginWord.length
  • beginWordendWord 和 wordList[i] 由小写英文字母组成
  • beginWord != endWord
  • wordList 中的所有字符串 互不相同
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        HashSet<String> wordSet = new HashSet<>(wordList); //转换为hashset 加快速度
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {  //特殊情况判断
            return 0;
        }
        Queue<String> queue = new LinkedList<>(); //bfs 队列
        queue.offer(beginWord);
        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(); //将单词转换为char array,方便替换
                for (char k = 'a'; k <= 'z'; k++) { //从'a' 到 'z' 遍历替换
                    chars[i] = k; //替换第i个字符
                    String newWord = String.valueOf(chars); //得到新的字符串
                    if (newWord.equals(endWord)) {  //如果新的字符串值与endWord一致,返回当前长度+1
                        return path + 1;
                    }
                    if (wordSet.contains(newWord) && !map.containsKey(newWord)) { //如果新单词在set中,但是没有访问过
                        map.put(newWord, path + 1); //记录单词对应的路径长度
                        queue.offer(newWord);//加入队尾
                    }
                }
            }
        }
        return 0; //未找到
    }
}

841.钥匙和房间

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 号房间。

提示:

  • n == rooms.length
  • 2 <= n <= 1000
  • 0 <= rooms[i].length <= 1000
  • 1 <= sum(rooms[i].length) <= 3000
  • 0 <= rooms[i][j] < n
  • 所有 rooms[i] 的值 互不相同

这道题目用dfs或者bfs都可以,思路就是对整个图进行搜索,用一个visited数组来标记是否被访问过,最后遍历整个集合看看是否每个元素都被访问过。 

dfs: 

class Solution {
    boolean[] visited; // 记录房间是否被访问过的数组

    /**
     * 判断是否能够访问所有房间的方法
     * @param rooms 房间的列表
     * @return 是否能够访问所有房间
     */
    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]){
                return false;
            }
        }
        return true;
    }

    /**
     * 深度优先搜索方法
     * @param rooms 房间的列表
     * @param node 当前节点(房间)
     */
    public void dfs(List<List<Integer>> rooms, int node){
        if(visited[node]){ // 如果当前节点已经被访问过,则返回
            return;
        }
        visited[node] = true; // 将当前节点标记为已访问
        // 遍历当前房间中的钥匙,继续向下搜索
        for(int i:rooms.get(node)){
            dfs(rooms, i);
        }
    }
}

 bfs:

class Solution {
    /**
     * 判断是否能够访问所有房间的方法
     * @param rooms 房间的列表,每个房间中包含了可以访问的其他房间的钥匙列表
     * @return 是否能够访问所有房间
     */
    public boolean canVisitAllRooms(List<List<Integer>> rooms) {
        boolean[] visited = new boolean[rooms.size()]; // 用一个 visited 数组记录房间是否被访问
        visited[0] = true; // 将第 0 个房间标记为已访问
        Queue<Integer> queue = new LinkedList<>(); // 使用队列进行广度优先搜索
        queue.add(0); // 将第 0 个房间加入队列
        // 遍历队列中的房间,查看它们可以访问的其他房间
        while (!queue.isEmpty()) {
            int curKey = queue.poll(); // 取出队首的房间
            // 遍历当前房间中的钥匙,将未访问过的房间加入队列
            for (int key: rooms.get(curKey)) {
                if (visited[key]) continue; // 如果房间已经访问过,则跳过
                visited[key] = true; // 标记房间为已访问
                queue.add(key); // 将该房间加入队列
            }
        }
        // 检查是否所有房间都被访问过
        for (boolean key: visited) {
            if (!key) return false; // 如果有房间未被访问过,则返回 false
        }
        return true; // 如果所有房间都被访问过,则返回 true
    }
}

463.岛屿的周长 

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 个黄色的边

示例 2:

输入:grid = [[1]]
输出:4

示例 3:

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

提示:

  • row == grid.length
  • col == grid[i].length
  • 1 <= row, col <= 100
  • grid[i][j] 为 0 或 1

遍历数组直到找到一个陆地,根据题意,只有一个岛屿,那么我们dfs完这个陆地就可以跳出循环了,因为不会有更多的岛屿,在遍历寻找岛屿陆地的时候,如果遇到了海洋或者索引越界,就说明到达了陆地的边界,遇到一次就将周长加一。

class Solution {
    boolean[][] visited; // 二维数组,用于记录已访问的单元格
    int perimeter = 0; // 岛屿的周长
    
    // 计算岛屿周长的主要函数
    public int islandPerimeter(int[][] grid) {
        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(grid[i][j] == 1) { // 如果单元格包含陆地
                    dfs(grid, i, j); // 从该单元格开始执行深度优先搜索
                    return perimeter; // 搜索结束后返回周长
                }
            }
        }
        return perimeter; // 返回周长(如果没有找到陆地则为0)
    }
    
    // 深度优先搜索(DFS)函数,用于遍历岛屿
    public void dfs(int[][] grid, int i, int j) {
        // 基本情况:如果单元格越界或者是水域
        if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0) {
            perimeter++; // 增加周长
            return; // 退出DFS
        }
        
        if(visited[i][j]) {
            return; // 如果单元格已被访问过,则退出DFS
        }
        
        visited[i][j] = true; // 标记当前单元格为已访问
        // 递归访问相邻单元格
        dfs(grid, i - 1, j); // 上
        dfs(grid, i + 1, j); // 下
        dfs(grid, i, j - 1); // 左
        dfs(grid, i, j + 1); // 右
    }
}

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

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

相关文章

基于nginx 动态 URL反向代理的实现

背景&#xff1a; 我们在项目中在这样一个场景&#xff0c;用户需要使用固定的软件资源&#xff0c;这些资源是以服务器或者以容器形式存在的。 资源以webAPI方式在内网向外提供接口&#xff0c;资源分类多种类型&#xff0c;每种类型的资源程序和Wapi参数都一样。这些资源部属…

域名交易系统源码 无需授权即可正常使用,附带后台功能

域名交易系统已测试可正常使用免授权带后台 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/88949686 更多资源下载&#xff1a;关注我。

Unity 打包真机脚本丢失的问题

记录Bug Bug详情分析解决方案附录 Bug详情 项目中导入了UI Particle的Package,用于处理特效层级 unity 运行效果正常&#xff0c;打包真机后运行时发现特效并没有正确显示&#xff0c;真机Log如下图 需要接入查看真机Log工具的点这里 查看图中Log发现对应的Prefab上挂载的脚本…

uniApp中使用小程序XR-Frame创建3D场景(2)加载模型

上篇文章讲述了如何将XR-Frame作为子组件集成到uniApp中使用&#xff0c;只完成了简单的环境搭建&#xff0c;这篇文章讲解如何加载3D模型。 1 加入模型加载标签 在XR-Frame框架中&#xff0c;加载资源都是在wxml文件的标签中实现的。下面是wxml中完整的代码 index.wxml &l…

github vscode 笔记

目录 前言1. 新建代码库2. 下载代码到本地3. 更新代码并上传到github 前言 github方便多人协作维护代码。该笔记记录了下面三个过程&#xff1a; 在github上新建代码库&#xff0c;下载代码到本地&#xff0c;将更新代码并上传到github 1. 新建代码库 2. 下载代码到本地 链…

本地GPU调用失败问题解决1

一&#xff1a;发现问题 1、电脑环境参数&#xff1a; OMEN by Gaming Laptop Windows 11 家庭中文版 2th Gen Intel(R) Core(TM) i9-12900H 2.50 GHz NVIDIA GeForce RTX 3060 Laptop GPU 显存6G PyCharm 2023.1.1(Professional Edition) 2、PyCharm中检测GPU&#x…

web自动化测试系列-selenium的运行原理和常用方法介绍(二)

目录 1.selenium的运行原理 2.常用方法介绍 接上文 &#xff1a;web自动化测试系列-selenium的安装和运行(一)-CSDN博客 在上文中我们编写了一段简单的代码 &#xff0c;可以驱动浏览器访问百度并搜索关键字 。这里我们再把这段代码再拿来加以说明 。 # 1. 导包 from selen…

Linux Tomcat的服务器如何查看接口请求方式?

问题描述 最近在和安卓开发对接接口&#xff0c;遇到一个接口总是报405错误&#xff0c;有对接经验的开发应该都知道是请求方式不对&#xff0c;假如接口定义为POST请求的&#xff0c;但是客户端却用GET请求&#xff0c;这时候就会报这个错误。Android客户端那边使用xUtils框架…

【AutoML】一个用于图像、文本、时间序列和表格数据的AutoML

一个用于图像、文本、时间序列和表格数据的AutoML AutoGluon介绍安装AutoGluon快速上手 参考资料 AutoGluon自动化机器学习任务&#xff0c;使您能够在应用程序中轻松实现强大的预测性能。只需几行代码就可以训练和部署有关图像&#xff0c;文本&#xff0c;时间序列和表格数据…

搭建Spark单机版环境

在搭建Spark单机版环境的实战中&#xff0c;首先确保已经安装并配置好了JDK。然后&#xff0c;从群共享下载Spark安装包&#xff0c;并将其上传至目标主机的/opt目录。接着&#xff0c;解压Spark安装包至/usr/local目录&#xff0c;并配置Spark的环境变量&#xff0c;以确保系统…

计算机网络:物理层 - 编码与调制

计算机网络&#xff1a;物理层 - 编码与调制 基本概念编码不归零制编码归零制编码曼彻斯特编码差分曼彻斯特编码 调制调幅调频调相混合调制 基本概念 在计算机网络中&#xff0c;计算机需要处理和传输用户的文字、图片、音频和视频&#xff0c;他们可以统称为消息数据&#xf…

C#学习笔记3:Windows窗口计时器

今日继续我的C#学习之路&#xff0c;今日学习自己制作一个Windows窗口计时器程序&#xff1a; 文章提供源码解释、步骤操作、整体项目工程下载 完成后的效果大致如下&#xff1a;&#xff08;可选择秒数&#xff0c;有进度条&#xff0c;开始计时按钮等&#xff09; &#xf…

一周学会Django5 Python Web开发-Django5模型定义

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计41条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

并发VS并行

参考文章 面试必考的&#xff1a;并发和并行有什么区别&#xff1f; 并发&#xff1a;一个人同时做多件事&#xff08;射击游戏队友抢装备&#xff09; 并行&#xff1a;多人同时处理同一件事&#xff08;射击游戏敌人同时射击对方&#xff09;

业务服务:xss攻击

文章目录 前言一、使用注解预防1. 添加依赖2. 自定义注解3. 自定义校验逻辑4. 使用 二、使用过滤器1. 添加配置2. 创建配置类3. 创建过滤器4. 创建过滤器类5. 使用 前言 xss攻击时安全领域中非常常见的一种方法&#xff0c;保证我们的系统安全是非常重要的 xss攻击简单来说就…

酷开科技:OTT领域的璀璨明珠

在广袤的科技海洋中&#xff0c;酷开科技犹如一颗璀璨的明珠&#xff0c;以其独特的魅力和卓越的实力&#xff0c;引领着OTT领域的发展。自2014年前后&#xff0c;彩电业遭遇三十年来首次的销量滑坡&#xff0c;整个行业陷入了“寒冬”。在这个艰难的时刻&#xff0c;酷开科技却…

pure-admin

vue-pure-admin: &#x1f525; 全面ESMVue3ViteElement-PlusTypeScript编写的一款后台管理系统&#xff08;兼容移动端&#xff09;

GBU3510-ASEMI开关电源整流桥GBU3510

编辑&#xff1a;ll GBU3510-ASEMI开关电源整流桥GBU3510 型号&#xff1a;GBU3510 品牌&#xff1a;ASEMI 封装&#xff1a;GBU-4 平均正向整流电流&#xff08;Id&#xff09;&#xff1a;35A 最大反向击穿电压&#xff08;VRM&#xff09;&#xff1a;1000V 产品引线…

【jenkins+cmake+svn管理c++项目】windows修改jenkins的工作目录

jenkins默认的存放源码的workspace是&#xff1a; C:\Users\用户\AppData\Local\Jenkins\.jenkins\workspace。由于jenkins会拉取大量的源代码以及编译生成一些文件&#xff0c;我希望我能自己指定目录作为它的工作空间&#xff0c;放在这里显然不太合适。 那么修改目录的方式有…

windows允许指定IP段访问本地端口

虚拟机内部应用有时候需要访问windows的一些端口&#xff0c;例如数据库或Redis等&#xff0c;默认情况下&#xff0c;需关闭windows上的防火墙才可正常访问。本文通过在防火墙设置允许指定IP段进行访问来处理&#xff0c;不用每次操作都关闭防火墙。 入站规则-》新建规则 完成…