题目来源
题目描述
class Solution {
public:
void solve(vector<vector<char>>& board) {
}
};
题目解析
递归
本题中的矩阵中有三种元素:
- 字母
X
- 被字母
X
包围的字母O
- 没有被字母
X
包围的字母O
本题要求将所有被字母X
包围的字母O
都变为X
,但是很难判断哪些O
是被包围的,哪些O
不是被包围的。
本题思考方向应该是,两种不同状态的 O 之间的不同区别。那么题,本质是:一个矩阵中有 3 个不同的元素,但是目前有2种元素(O)被混淆在一起,需要把这三种元素区分开来。
解题的重点放在2种不同 的 O 之间的区别:
题目中解释说:任何边界上的O
都不会被填充为X
。我们可以想到,所有的不被包围的O
都直接或者间接的与边界上的O
相连,因此问题转换为,如何寻找和边界联通的 O。
X X X X
X O O X
X X O X
X O O X
我们可以利用这个性质判断O
是否在边界上,具体的说:
- 对于每一个边界上的
O
,我们以它的起点,标记所有与它直接或者间接相连的字母O
(从边缘开始感染) - 遍历这个矩阵,对于每一个字母
- 如果该字母被标记过,则该字母为没有被字母 X 包围的字母 O,我们将其还原为字母 O;
- 如果该字母没有被标记过,则该字母为被字母 X 包围的字母 O,我们将其修改为字母 X。
class Solution {
/*
Solution s;
vector<vector<char>> board;
board.push_back({'X', 'O', 'X', 'O', 'X', '0'});
board.push_back({'O', 'X', 'O', 'X', 'O', 'X'});
board.push_back({'X', 'O', 'X', 'O', 'X', '0'});
board.push_back({'O', 'X', 'O', 'X', 'O', 'X'});
s.solve(board);
* */
void dfs(vector<vector<char>>& grid, int x, int y){
if(x < 0 || y < 0 || x >= grid.size() || y >= grid[0].size() || grid[x][y] != 'O'){
return;
}
grid[x][y] = '#';
dfs(grid, x - 1, y);
dfs(grid, x + 1, y);
dfs(grid, x, y - 1);
dfs(grid, x, y + 1);
}
public:
void solve(vector<vector<char>>& board){
int m = board.size();
if(m == 0){
return;
}
int n = board[0].size();
for (int i = 0; i < m; ++i) {
if(board[i][0] == 'O'){
dfs(board, i, 0);
}
if(board[i][n - 1] == 'O'){
dfs(board, i, n - 1);
}
}
for (int j = 0; j < n; ++j) {
if(board[0][j] == 'O'){
dfs(board, 0, j);
}
if(board[m - 1][j] == 'O'){
dfs(board, m - 1, j);
}
}
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if(board[i][j] == 'O'){
board[i][j] = 'X';
}
if(board[i][j] == '#'){
board[i][j] = 'O';
}
}
}
}
};
class Solution {
public:
void solve(vector<vector<char>>& board) {
if (board.empty()) return;
int rows = board.size(), cols = board[0].size();
// 从边界开始查找,转换
for (int i = 0; i < rows; ++i) {
if (board[i][0] == 'O') bfs(board, i, 0);
if (board[i][cols - 1] == 'O') bfs(board, i, cols - 1);
}
for (int i = 0; i < cols; ++i) {
if (board[0][i] == 'O') bfs(board, 0, i);
if (board[rows - 1][i] == 'O') bfs(board, rows - 1, i);
}
// 恢复转换 和 按题意设置 X元素
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (board[i][j] == 'O') board[i][j] = 'X';
if (board[i][j] == '#') board[i][j] = 'O';
}
}
}
private:
void bfs(vector<vector<char>>& board, int i, int j) {
int m = board.size(), n = board[0].size();
deque<pair<int, int>> worker{ {i,j} };
while (worker.size()) {
auto sub = worker.front();
auto &row = sub.first, &col = sub.second;
worker.pop_front();
if (row < 0 || row == m || col < 0 || col == n
|| board[row][col] != 'O') continue;
board[row][col] = '#';
worker.push_back({row + 1, col});
worker.push_back({row - 1, col});
worker.push_back({row, col + 1});
worker.push_back({row, col - 1});
}
}
};
并查集
注意,这里必须是四面被围的0
才能被换成X
,也就是说边角上的O
一定不会被围,进一步,与边角上的O
相连的O
也不会被X
包围,因此不会被替换
那怎么实现并查集呢?
- 这里,我们Union-Find底层用一个一维数组来实现(当然也可以二维),构造函数需要传入这个数组的大小,而题目给的是一个二维棋盘。
- 那么将二维数组怎么用一维数组表示呢?二维数组(x, y)可以转换成x * n + y 这个数(m是棋盘的行数,n是棋盘的列树)。这是二维坐标映射到一维的常用技巧
- 另外,怎么表示【祖师爷】呢?我们之前描述的「祖师爷」 是虚构的, 需要给他留个位置。索引 [ 0... m ∗ n − 1 ] [0... m*n - 1] [0...m∗n−1]都是棋盘内坐标的一维映射,那么就让这个虚拟的dummy节点占据索引 m ∗ n m * n m∗n就好了
怎么做?
(1)定义一个坐标转换函数:
static int index(int i, int j, int w){
//x、y 表示二维坐标值,w 表示矩阵宽度。
return i * w + j;
}
(2)初始化并查集,大小为矩阵的大小加一,多出的哪一个作为所有边界节点的父节点dummy
(3)遍历二维数组,将与边界相连的O与dummy联通
(4)遍历二维数组,将与dummy不相连的O改成X
class Solution {
class UnionFind{
private:
std::vector<int> parent;
std::vector<int> size;
std::vector<int> help;
int cnt;
public:
UnionFind(int N){
cnt = N;
parent.resize(N);
size.resize(N);
help.resize(N);
for (int i = 0; i < N; ++i) {
parent[i] = i;
size[i] = 1;
}
}
void merge(int i, int j){
int ri = findRoot(i);
int rj = findRoot(j);
if(ri != rj){
if(size[ri] >= size[rj]){
parent[rj] = ri;
size[ri] += size[rj];
}else{
parent[ri] = rj;
size[rj] += size[ri];
}
--cnt;
}
}
bool isConnected(int i, int j){
return findRoot(i) == findRoot(j);
}
private:
int findRoot(int i){
int hi = 0;
while (parent[i] != i){
help[hi++] = i;
i = parent[i];
}
for (hi--; hi >= 0; --hi) {
parent[help[hi]] = i;
}
return i;
}
};
private:
static int index(int i, int j, int w){
//x、y 表示二维坐标值,w 表示矩阵宽度。
return i * w + j;
}
public:
void solve(vector<vector<char>>& board) {
if(board.empty()){
return;
}
int m = board.size(); // 矩阵行数
int n = board[0].size(); // 矩阵列数(宽度),即第一行元素数
UnionFind unionFind(m * n + 1); // 初始化并查集,大小为矩阵大小+1, 给 dummy 留⼀个额外位置
int dummy = m * n; // dummy索引
// 与边角上的`O`相连的 O 与 dummy 连通
std::vector<std::vector<int>> dirs {{1, 0},{-1, 0},{0, 1}, {0, -1}};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'O') {
// 边界节点
if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
unionFind.merge(index(i, j, n), dummy);
} else {
// 非边界节点
for (auto dir : dirs) {
int x = i + dir[0];
int y = j + dir[1];
if (board[x][y] == 'O') {
unionFind.merge(index(i, j, n), index(x, y, n));
}
}
}
}
}
}
// 所有不和 dummy 连通的 O, 都要被替换
for (int i = 1; i < m - 1; i++){
for (int j = 1; j < n - 1; j++){
if (board[i][j] == 'O'){
if (!unionFind.isConnected(index(i, j, n), dummy)){
board[i][j] = 'X';
}
}
}
}
}
};
二维数组转一维数组
#include<stdio.h>
#include<stdlib.h>
//二维数组转一维
/*以列为主的二维阵列要转为一维阵列时,是将二维阵列由上往下一列一列读入一维阵列,此时
索引的对应公式如下所示,其中row与column是二维阵列索引,loc表示对应的一维阵列索引:
loc = column + row*行数*/
int main()
{
int num[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
printf("原二维数组:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%4d", num[i][j]);
}
printf("\n");
}
printf("进行转换:\n");
int number[3*3];
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
number[j+i*3] =num[i][j];
}
}
for (int i = 0; i < 3 * 3; i++)
printf("%d", number[i]);
printf("\n");
return 0;
}
类似题目
题目 | 思路 |
---|---|
leetcode:130. 被围绕的区域 Surrounded Regions | |
leetcode:695. 岛屿的最大面积Max Area of Island | |
leetcode:463. 岛屿的周长 Island Perimeter | 岛屿的周长就是岛屿方格和非岛屿方格相邻的边的数量。 |
leetcode:200. 岛屿数量 Number of Islands | dfs、并查集 |
leetcode:305. 岛屿的数量 Number of Islands II | |
leetcode:694. 不同岛屿的数量 Number of Distinct Islands | |
leetcode:711.不同岛屿的数量 II Number of Distinct Islands II | |
leetcode:1020. 飞地的数量 number-of-enclaves | |
leetcode:1254. 统计封闭岛屿的数目 number-of-closed-islands | 只要提前把靠边的陆地都淹掉,然后算出来的就是封闭岛屿了。 |
leetcode:1905. 统计子岛屿 count-sub-islands | |
leetcode:286.给每个空房间位上填上该房间到 最近 门的距离 Walls and Gates | 从门开始扩散 |
leetcode:489. 扫地机器人 Robot Room Cleaner | 难点:怎么建立位置坐标?怎么回溯? |
leetcode:317. 离建筑物最近的距离 Shortest Distance from All Buildings | (1) 从每一个建筑物开始进行广度优先搜索 (2) 在搜索的同时计算每一个空格到这个建筑物的距离 (3) 在搜索的同时将每一个空格到每一个建筑物的距离进行累加,得到每个空格到所有建筑物的距离(4) 取空格到所有建筑物的最小距离 |
leetcode:323. 无向连通图中的连通分量个数Number of Connected Components in an Undirected Graph | 连通图 |
Rotting Oranges |