給定一個二維的矩陣,包含 'X'
和 'O'
(字母 O)。java
找到全部被 'X'
圍繞的區域,並將這些區域裏全部的 'O'
用 'X'
填充。node
示例:面試
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'
。若是兩個元素在水平或垂直方向相鄰,則稱它們是「相連」的。分佈式
這道題咱們拿到基本就能夠肯定是圖的dfs、bfs遍歷的題目了。題目中解釋說被包圍的區間不會存在於邊界上,因此咱們會想到邊界上的o要特殊處理,只要把邊界上的o特殊處理了,那麼剩下的o替換成x就能夠了。問題轉化爲,如何尋找和邊界聯通的o,咱們須要考慮以下狀況。函數
X X X X X O O X X X O X X O O X
這時候的o是不作替換的。由於和邊界是連通的。爲了記錄這種狀態,咱們把這種狀況下的o換成#做爲佔位符,待搜索結束以後,遇到o替換爲x(和邊界不連通的o);遇到#,替換回o(和邊界連通的o)。this
如何尋找和邊界聯通的o? 從邊界出發,對圖進行dfs和bfs便可。這裏簡單總結下dfs和dfs。spa
那麼基於上面這種想法,咱們有四種方式實現。code
class Solution { public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 從邊緣o開始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { dfs(board, i, 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'; } } } } public void dfs(char[][] board, int i, int j) { if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') { // board[i][j] == '#' 說明已經搜索過了. return; } board[i][j] = '#'; dfs(board, i - 1, j); // 上 dfs(board, i + 1, j); // 下 dfs(board, i, j - 1); // 左 dfs(board, i, j + 1); // 右 } }
非遞歸的方式,咱們須要記錄每一次遍歷過的位置,咱們用stack來記錄,由於它先進後出的特色。而位置咱們定義一個內部類Pos來標記橫座標和縱座標。注意的是,在寫非遞歸的時候,咱們每次查看stack頂,可是並不出stack,直到這個位置上下左右都搜索不到的時候出Stack。
class Solution { public class Pos{ int i; int j; Pos(int i, int j) { this.i = i; this.j = j; } } public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 從邊緣第一個是o的開始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { dfs(board, i, 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'; } } } } public void dfs(char[][] board, int i, int j) { Stack<Pos> stack = new Stack<>(); stack.push(new Pos(i, j)); board[i][j] = '#'; while (!stack.isEmpty()) { // 取出當前stack 頂, 不彈出. Pos current = stack.peek(); // 上 if (current.i - 1 >= 0 && board[current.i - 1][current.j] == 'O') { stack.push(new Pos(current.i - 1, current.j)); board[current.i - 1][current.j] = '#'; continue; } // 下 if (current.i + 1 <= board.length - 1 && board[current.i + 1][current.j] == 'O') { stack.push(new Pos(current.i + 1, current.j)); board[current.i + 1][current.j] = '#'; continue; } // 左 if (current.j - 1 >= 0 && board[current.i][current.j - 1] == 'O') { stack.push(new Pos(current.i, current.j - 1)); board[current.i][current.j - 1] = '#'; continue; } // 右 if (current.j + 1 <= board[0].length - 1 && board[current.i][current.j + 1] == 'O') { stack.push(new Pos(current.i, current.j + 1)); board[current.i][current.j + 1] = '#'; continue; } // 若是上下左右都搜索不到,本次搜索結束,彈出stack stack.pop(); } } }
dfs非遞歸的時候咱們用stack來記錄狀態,而bfs非遞歸,咱們則用隊列來記錄狀態。和dfs不一樣的是,dfs中搜索上下左右,只要搜索到一個知足條件,咱們就順着該方向繼續搜索,因此你能夠看到dfs代碼中,只要知足條件,就入Stack,而後continue本次搜索,進行下一次搜索,直到搜索到沒有知足條件的時候出stack。而bfs中,咱們要把上下左右知足條件的都入隊,因此搜索的時候就不能continue。你們能夠對比下二者的代碼,體會bfs和dfs的差別。
class Solution { public class Pos{ int i; int j; Pos(int i, int j) { this.i = i; this.j = j; } } public void solve(char[][] board) { if (board == null || board.length == 0) return; int m = board.length; int n = board[0].length; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 從邊緣第一個是o的開始搜索 boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; if (isEdge && board[i][j] == 'O') { bfs(board, i, 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'; } } } } public void bfs(char[][] board, int i, int j) { Queue<Pos> queue = new LinkedList<>(); queue.add(new Pos(i, j)); board[i][j] = '#'; while (!queue.isEmpty()) { Pos current = queue.poll(); // 上 if (current.i - 1 >= 0 && board[current.i - 1][current.j] == 'O') { queue.add(new Pos(current.i - 1, current.j)); board[current.i - 1][current.j] = '#'; // 沒有continue. } // 下 if (current.i + 1 <= board.length - 1 && board[current.i + 1][current.j] == 'O') { queue.add(new Pos(current.i + 1, current.j)); board[current.i + 1][current.j] = '#'; } // 左 if (current.j - 1 >= 0 && board[current.i][current.j - 1] == 'O') { queue.add(new Pos(current.i, current.j - 1)); board[current.i][current.j - 1] = '#'; } // 右 if (current.j + 1 <= board[0].length - 1 && board[current.i][current.j + 1] == 'O') { queue.add(new Pos(current.i, current.j + 1)); board[current.i][current.j + 1] = '#'; } } } }
bfs 通常咱們不會去涉及,並且比較繞,以前咱們惟一A過的用bfs遞歸的方式是層序遍歷二叉樹的時候能夠用遞歸的方式。
並查集這種數據結構好像你們不太經常使用,實際上頗有用,我在實際的production code中用過並查集。並查集經常使用來解決連通性的問題,即將一個圖中連通的部分劃分出來。當咱們判斷圖中兩個點之間是否存在路徑時,就能夠根據判斷他們是否在一個連通區域。 而這道題咱們其實求解的就是和邊界的O在一個連通區域的的問題。
並查集的思想就是,同一個連通區域內的全部點的根節點是同一個。將每一個點映射成一個數字。先假設每一個點的根節點就是他們本身,而後咱們以此輸入連通的點對,而後將其中一個點的根節點賦成另外一個節點的根節點,這樣這兩個點所在連通區域又相互連通了。
並查集的主要操做有:
class UnionFind { int[] parents; public UnionFind(int totalNodes) { parents = new int[totalNodes]; for (int i = 0; i < totalNodes; i++) { parents[i] = i; } } // 合併連通區域是經過find來操做的, 即看這兩個節點是否是在一個連通區域內. void union(int node1, int node2) { int root1 = find(node1); int root2 = find(node2); if (root1 != root2) { parents[root2] = root1; } } int find(int node) { while (parents[node] != node) { // 當前節點的父節點 指向父節點的父節點. // 保證一個連通區域最終的parents只有一個. parents[node] = parents[parents[node]]; node = parents[node]; } return node; } boolean isConnected(int node1, int node2) { return find(node1) == find(node2); } }
咱們的思路是把全部邊界上的O看作一個連通區域。遇到O就執行並查集合並操做,這樣全部的O就會被分紅兩類
因爲並查集咱們通常用一維數組來記錄,方便查找parants,因此咱們將二維座標用node函數轉化爲一維座標。
public void solve(char[][] board) { if (board == null || board.length == 0) return; int rows = board.length; int 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'; } } } } int node(int i, int j) { return i * cols + j; } }