leetcode
leetcode 101 ~ 150
被围绕的区域

被围绕的区域

难度:

标签:

题目描述

给你一个 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'

代码结果

运行时间: 20 ms, 内存: 20.5 MB


/*
  思路:
  使用Stream API来处理二维数组的操作。因为Stream API不直接支持二维数组操作,这里主要是通过传统遍历后转换为Stream来实现
*/
import java.util.stream.Stream;
import java.util.stream.IntStream;
 
public class SolutionStream {
    public void solve(char[][] board) {
        if (board == null || board.length == 0) return;
        int m = board.length;
        int n = board[0].length;
        // 标记边界上的'O'以及与其相连的'O'
        Stream.concat(
            IntStream.range(0, m).boxed(),
            IntStream.range(0, n).boxed()
        ).forEach(i -> {
            if (i < m && board[i][0] == 'O') dfs(board, i, 0);
            if (i < m && board[i][n - 1] == 'O') dfs(board, i, n - 1);
            if (i < n && board[0][i] == 'O') dfs(board, 0, i);
            if (i < n && board[m - 1][i] == 'O') dfs(board, m - 1, i);
        });
        // 将所有的'O'变为'X'
        IntStream.range(0, m).forEach(i ->
            IntStream.range(0, n).forEach(j -> {
                if (board[i][j] == 'O') board[i][j] = 'X';
            })
        );
        // 将所有的'*'还原为'O'
        IntStream.range(0, m).forEach(i ->
            IntStream.range(0, n).forEach(j -> {
                if (board[i][j] == '*') board[i][j] = 'O';
            })
        );
    }
    // 深度优先搜索,标记与边界'O'相连的'O'
    private void dfs(char[][] board, int i, int j) {
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != 'O') return;
        board[i][j] = '*';
        dfs(board, i + 1, j);
        dfs(board, i - 1, j);
        dfs(board, i, j + 1);
        dfs(board, i, j - 1);
    }
}
 

解释

方法:

这个题解采用了深度优先搜索(DFS)的方法。首先对矩阵的四条边界上的 'O' 进行 DFS,标记所有与边界 'O' 相连的 'O',这些 'O' 不会被填充为 'X'。然后遍历矩阵内部,将所有未被标记的 'O' 填充为 'X'。

时间复杂度:

O(mn)

空间复杂度:

O(mn)

代码细节讲解

🦆
题解中提到的DFS的递归深度最大为m+n,请问这是如何算出来的?是否有可能超过这个深度?
递归深度的估计是基于最坏情况下,从一个边界的'O'开始,沿着可能的路径一直搜索到矩阵的另一个边界。在一个m行n列的矩阵中,最长的搜索路径可能是从矩阵的一个角开始,对角线到达另一个角,此路径长度大约是m+n。但实际上,由于DFS会从四个方向进行扩展,遍历的路径可能会因为路径重叠而少于m+n。严格来说,这个估计是一个上限,实际使用的递归深度可能会小于这个值,但不会超过,除非有环形的连通区域,这在本题的情境中是不会出现的。
🦆
在DFS中,为什么需要判断`visited[row][col]`值为False才进行递归搜索?
在DFS中,使用`visited`数组是为了防止重复访问相同的节点,这可以避免无限循环和不必要的重复计算。每个节点(即矩阵中的每个位置)只需要被访问一次,以判断它是否与边界的'O'直接或间接相连。如果不使用`visited`标记,DFS可能会反复进入已访问过的节点,导致递归无法正确结束,程序效率也会显著降低。
🦆
题解提到对四条边界上的'O'进行DFS,为什么这种策略能确保所有与边界相连的'O'都不会被填充为'X'?
这种策略的基本思想是:只有与边界上的'O'直接或间接相连的内部'O'才是安全的,不会被包围。因此,从边界上的每个'O'出发进行DFS,可以标记所有这些安全的'O'。通过这种方式,所有从边界可达的'O'都会被访问并标记,而那些没有被标记的'O'则意味着它们是被完全包围的,可以安全地被转换为'X'。
🦆
如果矩阵的尺寸非常大,DFS可能会导致栈溢出。是否有其他方法可以避免这种情况?
确实,对于非常大的矩阵,使用递归的DFS可能会导致栈溢出。一个可行的替代方法是使用广度优先搜索(BFS),它使用队列而不是调用栈来处理节点,这可以有效防止栈溢出问题。BFS按层次扩散,每次处理所有当前层的节点,然后移动到下一层,这样可以避免深度递归导致的栈溢出风险。另一个方法是将递归DFS转换为迭代DFS,通过手动维护一个栈来模拟递归调用,这样也可以控制栈的大小,避免溢出。

相关问题

岛屿数量

给你一个由 '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'

墙与门

墙与门