题目
给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' 组成,捕获 所有 被围绕的区域:
- 连接: 一个单元格与水平或垂直方向上相邻的单元格连接。
- 区域:连接所有
'O'的单元格来形成一个区域。 - 围绕: 如果您可以用
'X'单元格 连接这个区域,并且区域中没有任何单元格位于board边缘,则该区域被'X'单元格围绕。
通过将输入矩阵 board 中的所有 '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"]]
解释:
在上图中,底部的区域没有被捕获,因为它在 board 的边缘并且不能被围绕。
示例 2:
输入: board = [["X"]]
输出: [["X"]]
提示:
m == board.lengthn == board[i].length1 <= m, n <= 200board[i][j]为'X'或'O'
思路
本题三种元素:
- 字母"X"
- 被字母"X"包围的"O"
- 不被字母"X"包围的"O"
要求将所有被X包围的O变成X,如何判断O被包围或者不被包围。
题目解释中提到:任何边界上的 O 都不会被填充为 X。 我们可以想到,所有的不被包围的 O 都直接或间接与边界上的 O 相连。我们可以利用这个性质判断 O 是否在边界上。
具体地说:
- 对于每一个边界上的 O,我们以它为起点,标记所有与它直接或间接相连的字母 O;
- 最后我们遍历这个矩阵,对于每一个字母:
- 如果该字母被标记过,则该字母为没有被字母 X 包围的字母 O,我们将其还原为字母 O;
- 如果该字母没有被标记过,则该字母为被字母 X 包围的字母 O,我们将其修改为字母 X。
解法一: DFS
解法一: DFS
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
def dfs(r, c):
if r < 0 or r >= nr or c < 0 or c >= nc or board[r][c] != 'O':
return
# 标记
board[r][c] = 'A'
dfs(r+1, c)
dfs(r-1, c)
dfs(r, c+1)
dfs(r, c-1)
nr, nc = len(board), len(board[0])
for r in range(nr):
dfs(r, 0)
dfs(r, nc-1)
for c in range(nc):
dfs(0, c)
dfs(nr-1, c)
for r in range(nr):
for c in range(nc):
if board[r][c] == "A":
board[r][c] = 'O'
elif board[r][c] == 'O':
board[r][c] = 'X'
解法二: BFS
解法二: BFS
import collections
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
nr, nc = len(board), len(board[0])
q = collections.deque()
for r in range(nr):
if board[r][0] == "O":
q.append((r, 0))
board[r][0] = "A"
if board[r][nc-1] == "O":
q.append((r, nc-1))
board[r][nc-1] = "A"
for c in range(nc):
if board[0][c] == "O":
q.append((0, c))
board[0][c] = "A"
if board[nr-1][c] == "O":
q.append((nr-1, c))
board[nr-1][c] = "A"
while q:
r, c = q.popleft()
for next_r, next_c in [(r+1, c), (r-1, c), (r, c+1), (r, c-1)]:
if next_r >= 0 and next_r < nr and next_c >= 0 and next_c < nc and board[next_r][next_c] == 'O':
q.append((next_r, next_c))
board[next_r][next_c] = 'A'
for r in range(nr):
for c in range(nc):
if board[r][c] == "A":
board[r][c] = 'O'
elif board[r][c] == 'O':
board[r][c] = "X"
解法三: 并查集
将四周的O和哨兵连接起来,然后遍历数组将所有与边界联通的连接。最后遍历board,判断当前的'0'是否和哨兵连接,如果无连接,说明被X完全包围,将O变为X。
解法三: 并查集
class Solution:
def solve(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
nr, nc = len(board), len(board[0])
dummyNode = nr * nc
uf = UnionFind(nr*nc+1)
for r in range(nr):
for c in range(nc):
if board[r][c] == 'O':
if r == 0 or r == nr-1 or c == 0 or c == nc-1:
uf.union(r*nc+c, dummyNode)
else:
if r-1 >= 0 and board[r-1][c] == "O":
uf.union(r*nc+c, (r-1)*nc+c)
if r+1 < nr and board[r+1][c] == "O":
uf.union(r*nc+c, (r+1)*nc+c)
if c-1 >= 0 and board[r][c-1] == "O":
uf.union(r*nc+c, r*nc+c-1)
if c+1 < nc and board[r][c+1] == "O":
uf.union(r*nc+c, r*nc+c+1)
for r in range(nr):
for c in range(nc):
if not uf.isConnected(r*nc+c, dummyNode):
board[r][c] = 'X'
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
def find(self, x):
if self.parent[x] != x:
# 如果只是简单的查找,会超时,在查找时最好赋值,下次查找更便捷
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
self.parent[rootx] = rooty
def isConnected(self, x, y):
return self.find(x) == self.find(y)