被围绕的区域
涉及知识:深度优先搜索,广度优先搜索,并查集
分析:
题目并不难,该处主要提供两种解法:广搜和并查集。
广搜:
由题意可知,只要与边界上值为 ‘O’ 的点连通的结点均不会被 ‘X’ 围绕,反之该点会被 ‘X’ 围绕,需要将其改为 ‘X’;
因此,一个直观的想法便是首先找出所有与边界上 ‘O’ 相连的结点,并将其进行标记(假设标记为 ‘S’),然后遍历该矩阵,如果某点被标记,那么说明该点与边界相通,不会被围绕,将其改回 ‘O’ ;如果某点为 ‘O’ 说明该节点会被围绕,将其修改为 ‘X’;
那么如何找出与边界上 ‘O’ 结点相连的 ‘O’ 结点呢,我们可以使用广度优先搜索,将所有边界上的 ‘O’ 结点入队,然后依次判断其上、下、左、右结点是否满足条件(是 ‘O’ 结点),满足则标记入队,否则不做处理。
/*
* @lc app=leetcode.cn id=130 lang=java
*
* [130] 被围绕的区域
*/
class Solution {
private int[][] dir = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
private int height;
private int width;
private char[][] board;
//记录每一个结点
public class Node{
public int x;
public int y;
public Node(int x, int y){
this.x = x;
this.y = y;
}
}
public void solve(char[][] board) {
if(board == null || board.length == 0 || board[0].length == 0){
return;
}
this.width = board[0].length;
this.height = board.length;
this.board = board;
bfs();
return;
}
//广搜将与边相连的结点进行标记
public void bfs(){
Queue<Node> que = new LinkedList<>();
//上
for(int i = 0; i < width; ++i){
if(board[0][i] == 'O'){
que.offer(new Node(0, i));
}
}
//左
for(int i = 0; i < height; ++i){
if(board[i][0] == 'O'){
que.offer(new Node(i, 0));
}
}
//下
for(int i = 0; i < width; ++i){
if(board[height - 1][i] == 'O'){
que.offer(new Node(height - 1, i));
}
}
//右
for(int i = 0; i < height; ++i){
if(board[i][width - 1] == 'O'){
que.offer(new Node(i, width - 1));
}
}
//标记所有不会被围绕的结点
while(!que.isEmpty()){
Node tmp = que.peek();
que.poll();
board[tmp.x][tmp.y] = 'S';
for(int i = 0; i < dir.length; ++i){
if(Judge(tmp.x + dir[i][0], tmp.y + dir[i][1])){
que.offer(new Node(tmp.x + dir[i][0], tmp.y + dir[i][1]));
}
}
}
//最后判断是否被标记,,进而处理
for(int i = 0; i < height; ++i){
for(int j = 0; j < width; ++j){
if(board[i][j] == 'S'){
board[i][j] = 'O';
} else if(board[i][j] == 'O'){
board[i][j] = 'X';
}
}
}
return;
}
public boolean Judge(int x, int y){
if(x < 0 || x >= height || y < 0 || y >= width || board[x][y] == 'X' || board[x][y] == 'S'){
return false;
}
return true;
}
}
并查集
并查集常被用来解决连通性问题,本题中我们可以将所有边界上的 ‘O’ 看成连通,将所有的 ‘O’ 结点与其上下左右的 ‘O’ 结点连通,最后判断 ‘O’ 结点是否与边界上的 ‘O’ 相连,如果相连,则不会被围绕,否则会。
在次我们多引进一个结点, 用于将所有边界上的 ‘O’ 连接起来。
class Solution {
private int row;
private int col;
public void solve(char[][] board) {
if(board == null || board.length == 0 || board[0].length == 0){
return;
}
row = board.length;
col = board[0].length;
int help = row * col;
UnionSet unionSet = new UnionSet(help + 1);
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(board[i][j] == 'O'){
//如果是边界结点,则将其与 help 合并
if((i == 0 || j == 0 || i == row - 1 || j == col - 1)){
unionSet.Union(help, getId(i, j));
} else {
//否则与其上下左右的 O 进行合并
if(board[i - 1][j] == 'O'){
unionSet.Union(getId(i, j), getId(i - 1, j));
}
if(board[i + 1][j] == 'O'){
unionSet.Union(getId(i, j), getId(i + 1, j));
}
if(board[i][j - 1] == 'O'){
unionSet.Union(getId(i, j), getId(i, j - 1));
}
if(board[i][j + 1] == 'O'){
unionSet.Union(getId(i, j), getId(i, j + 1));
}
}
}
}
}
for(int i = 0; i < row; ++i){
for(int j = 0; j < col; ++j){
if(board[i][j] == 'O' && unionSet.findRoot(help) != unionSet.findRoot(getId(i, j))){
board[i][j] = 'X';
}
}
}
return;
}
public int getId(int x, int y){
return x * col + y;
}
/**
* 并查集
*/
public class UnionSet{
public int[] parent;
//构建一个大小为 num 的并查集
public UnionSet(int num){
parent = new int[num];
for(int i = 0; i < parent.length; ++i){
parent[i] = i;
}
}
public int findRoot(int root){
return (parent[root] == root) ? root : (parent[root] = findRoot(parent[root]));
}
public void Union(int x, int y){
int xRoot = findRoot(x);
int yRoot = findRoot(y);
if(xRoot != yRoot){
parent[xRoot] = yRoot;
}
return;
}
}
}