LC-130 被围绕的区域
问题转化:如何寻找和边界联通的 ‘O’。
1.先遍历最外圈(左右,上下),找到边界连通的 ‘O’。
- 找到后可以进行dfs递归或者用队列,栈进行坐标位置存储。
2.在递归中,或者在队列,栈中,对其四个方向进行遍历。
- 一直递归下去,或者类似二叉树层序遍历,一边遍历,一边往队列或栈中添加元素。
解题方法:
-
dfs
,递归。 -
dfs
,非递归,用栈。 -
bfs
,非递归,用队列。 -
并查集
,
// dfs, 递归
class Solution {
// 上下左右的偏移。{x,y}={row, col}。分别代表行和列。
private int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
private int row, col;
public void solve(char[][] board) {
if (board.length == 0) return;
row = board.length; // 行
col = board[0].length; // 列
// 左右边缘
for (int i = 0; i < row; i++){
dfs(board, i, 0);
dfs(board, i, col - 1);
}
// 上下边缘
for (int j = 0; j < col; j++) {
dfs(board, 0, j);
dfs(board, row - 1, j);
}
// 最后进行一次遍历
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++) {
if (board[i][j] == 'T'){
board[i][j] = 'O';
// else 不能少
} else if (board[i][j] == 'O'){
board[i][j] = 'X';
}
}
}
}
private void dfs(char[][] board, int r, int c) {
if (r < 0 || r >= row || c < 0 || c >= col || board[r][c] != 'O'){
return;
}
board[r][c] = 'T';
for (int[] d : directions){
dfs(board, r + d[0], c + d[1]);
}
}
}
// 深度优先, 非递归, (栈)
class Solution {
// row, col
private int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
private int row, col;
public void solve(char[][] board) {
if (board.length == 0) return;
row = board.length;
col = board[0].length;
// 遍历边缘
for (int i = 0; i < row; i++){
for (int j =0; j < col; j++){
boolean isEdge = i == 0 || j == 0 || i == row - 1 || j == col - 1;
if (isEdge && board[i][j] == 'O') {
dfs(board, i, j);
}
}
}
// 最后进行一次遍历
for (int i = 0; i < row; i++){
for (int j = 0; j < col; j++) {
if (board[i][j] == 'O'){
board[i][j] = 'X';
} else if (board[i][j] == 'T'){
board[i][j] = 'O';
}
}
}
}
private void dfs(char[][] board, int r, int c){
Stack<int[]> stack = new Stack<>();
stack.push(new int[]{r, c});
board[r][c] = 'T';
while (! stack.isEmpty()) {
// 栈顶元素,不弹出
int[] pos = stack.peek();
boolean flag = true;
// 上下左右四个元素进行遍历
for (int[] d : directions) {
int mr = pos[0] + d[0], mc = pos[1] + d[1];
if (mr < 0 || mr >= row || mc < 0 || mc >= col || board[mr][mc] != 'O'){
continue;
}
// if (){
stack.push(new int[]{mr, mc});
board[mr][mc] = 'T';
flag = false;
// }
}
// 当上一步找到了元素,则该元素不会被弹出。
if (flag){
stack.pop();
}
}
}
}
// bfs,非递归,(队列)
class Solution {
// row,col
private int[][] directions = {{-1, 0},{0, 1},{1, 0},{0, -1}};
private int row, col;
public void solve(char[][] board) {
if (board.length == 0) return;
row = board.length;
col = board[0].length;
// 存储边缘上的'O'的坐标
Queue<int[]> queue = new LinkedList<>();
// 左右边缘
for (int i = 0; i < row; i++){
if (board[i][0] == 'O'){
queue.offer(new int[]{i, 0});
board[i][0] = 'T';
}
if (board[i][col - 1] == 'O'){
queue.offer(new int[]{i, col - 1});
board[i][col - 1] = 'T';
}
}
// 上下边缘
for (int j = 1; j < col - 1; j++){
if (board[0][j] == 'O'){
queue.offer(new int[]{0, j});
board[0][j] = 'T';
}
if (board[row - 1][j] == 'O') {
queue.offer(new int[]{row - 1, j});
board[row - 1][j] = 'T';
}
}
// 对队列中存储的位置,进行上下左右四个方向的遍历
while (!queue.isEmpty()){
int[] pos = queue.poll();
int r = pos[0], c = pos[1];
for (int[] d : directions){
int mr = r + d[0], mc = c + d[1];
if (mr < 0 || mr >= row || mc < 0 || mc >= col || board[mr][mc] != 'O'){
continue;
}
queue.offer(new int[]{mr, mc});
board[mr][mc] = 'T';
}
}
// 最后进行一次遍历
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j] == 'O'){
board[i][j] = 'X';
} else if (board[i][j] == 'T') {
board[i][j] = 'O';
}
}
}
}
}
// 并查集,求连通区域
class UnionFind{
// 用一维数组表示并查集
int[] parents;
public UnionFind(int totalNodes) {
parents = new int[totalNodes];
for (int i = 0; i < totalNodes; i++) {
// 初始化父节点,下一个节点都指向自身
parents[i] = i;
}
}
// 合并两个节点所在的连通区域
public void union(int node1, int node2){
int root1 = find(node1);
int root2 = find(node2);
if (root1 != root2) {
parents[root2] = root1;
}
}
// 查找该节点的根节点
public int find(int node) {
// 父节点的特征,指向自己
while (parents[node] != node) {
// 父节点 指向 父节点的父节点
parents[node] = parents[parents[node]];
node = parents[node];
}
return node;
}
// 判断两个节点是否在一个连通区域中
boolean isConnected(int node1, int node2) {
return find(node1) == find(node2);
}
}
class Solution{
private int rows, cols;
public void solve(char[][] board) {
if (board == null || board.length == 0)
return;
rows = board.length;
cols = board[0].length;
// 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点
UnionFind uf = new UnionFind(rows * cols + 1);
int dummyNode = rows * cols;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (board[i][j] == 'O') {
// 遇到O进行并查集操作合并
if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) {
// 边界上的O,把它和dummyNode 合并成一个连通区域.
uf.union(node(i, j), dummyNode);
} else {
// 和上下左右合并成一个连通区域.
if (i > 0 && board[i - 1][j] == 'O')
uf.union(node(i, j), node(i - 1, j));
if (i < rows - 1 && board[i + 1][j] == 'O')
uf.union(node(i, j), node(i + 1, j));
if (j > 0 && board[i][j - 1] == 'O')
uf.union(node(i, j), node(i, j - 1));
if (j < cols - 1 && board[i][j + 1] == 'O')
uf.union(node(i, j), node(i, j + 1));
}
}
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (uf.isConnected(node(i, j), dummyNode)) {
// 和dummyNode 在一个连通区域的,那么就是O;
board[i][j] = 'O';
} else {
board[i][j] = 'X';
}
}
}
}
// 将二维坐标用表示成一维坐标
private int node(int i, int j) {
return i * cols + j;
}
}
代码小技巧:
// 定义一个二维数组,不需要显示的new一个对象。
// {row,col}
private int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
for (int[] d : directions) {
int mr = r + d[0], mc = c + d[1];
}
LC-547 省份数量
// dfs
class Solution {
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
int circle = 0;
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(isConnected, visited, n, i);
circle++;
}
}
return circle;
}
private void dfs(int[][] isConnected, boolean[] visited, int n, int i) {
for (int j = 0; j < n; j++) {
// 从0开始,包含了[i][i]坐标,可以将i设为true。
if (isConnected[i][j] == 1 && !visited[j]) {
visited[j] = true;
dfs(isConnected, visited, n, j);
}
}
}
}
// bfs
class Solution {
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
boolean[] visited = new boolean[n];
int circle = 0;
Queue<Integer> queue = new LinkedList<>();
// 每次广度优先遍历下去,连通的置为true且circle加1
// 第二次遍历时,已经标记过的不在进行遍历。
for (int i = 0; i < n; i++) {
if (!visited[i]) {
queue.offer(i);
while (!queue.isEmpty()) {
int j = queue.poll();
visited[j] = true;
for (int k = 0; k < n; k++) {
if (isConnected[j][k] == 1 && !visited[k]) {
queue.offer(k);
}
}
}
circle++;
}
}
return circle;
}
}
123
// 并查集
class Solution {
public int findCircleNum(int[][] isConnected) {
int provinces = isConnected.length;
int[] parent = new int[provinces];
for (int i = 0; i < provinces; i++) {
parent[i] = i;
}
for (int i = 0; i < provinces; i++) {
for (int j = i + 1; j < provinces; j++) {
if (isConnected[i][j] == 1) {
union(parent, i, j);
}
}
}
int circles = 0;
// 省份的数量就是,并查集中根节点的数量
for (int i = 0; i < provinces; i++) {
// 根节点
if (parent[i] == i) {
circles++;
}
}
return circles;
}
public void union(int[] parent, int index1, int index2) {
parent[find(parent, index1)] = find(parent, index2);
}
public int find(int[] parent, int index) {
// 如果不是根节点
if (parent[index] != index) {
parent[index] = find(parent, parent[index]);
}
return parent[index];
}
}