https://leetcode.com/problems/word-search/數組
Given a 2D board and a word, find if the word exists in the grid.app
The word can be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.函數
For example,
Given board =優化
[ ["ABCE"], ["SFCS"], ["ADEE"] ]
word = "ABCCED"
, -> returns true
,
word = "SEE"
, -> returns true
,
word = "ABCB"
, -> returns false
.spa
解題思路:debug
這道題也屬於DFS,能夠化解爲從matrix中任意點開始往下拓展的狀態中,能夠達到word嗎?下一狀態的可能性就是當前index的上下左右。可是超時是一個大問題,下面就是開始模仿前面DFS寫的代碼,自己有很多問題,可是放在IDE裏debug,是能得出正確答案的。不過TLE,百思不得其解code
public class Solution { boolean flag = false; public boolean exist(char[][] board, String word) { for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ int[][] visited = new int[board.length][board[0].length]; dfs(board, new StringBuffer(), word, new int[]{i, j}, visited); } } return flag; } public void dfs(char[][] board, StringBuffer currentWord, String word, int[] index, int[][] visited){ if(flag){ return; } if(currentWord.length() == word.length()){ if(currentWord.toString().equals(word)){ flag = true; } return; } if(word.charAt(currentWord.length()) != board[index[0]][index[1]]){ return; } visited[index[0]][index[1]] = 1; int[][] next_index = new int[4][2]; next_index[0][0] = index[0] - 1; next_index[0][1] = index[1]; next_index[1][0] = index[0] + 1; next_index[1][1] = index[1]; next_index[2][0] = index[0]; next_index[2][1] = index[1] - 1; next_index[3][0] = index[0]; next_index[3][1] = index[1] + 1; for(int i = 0; i < next_index.length; i++){ if(next_index[i][0] >= 0 && next_index[i][1] >= 0 && next_index[i][0] < board.length && next_index[i][1] < board[0].length && visited[next_index[i][0]][next_index[i][1]] == 0){ currentWord.append(board[index[0]][index[1]]); dfs(board, currentWord, word, new int[]{next_index[i][0], next_index[i][1]}, visited); currentWord.deleteCharAt(currentWord.length() - 1); } } } }
只能去網上參考網友的解答。下面的代碼是AC的,作了兩個修改。blog
第一,將第一處註釋的visited二維數組放到了最外面,由於最後會回溯,因此沒必要要每次開始都劃分一個新的數組,而聲明一個如此大的二維數組是很花時間的。遞歸
第二,在第二處註釋的判斷放在了遞歸函數的最前面。這樣使得遞歸的開始條件不合適就直接return,而不是在進入遞歸前就判斷是否要進入。注意,這纔是作DFS或者遞歸剪枝的正確方法。leetcode
下面AC的代碼花了450-480ms。
public class Solution { boolean flag = false; public boolean exist(char[][] board, String word) { int[][] visited = new int[board.length][board[0].length]; for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ // int[][] visited = new int[board.length][board[0].length]; dfs(board, new StringBuffer(), word, new int[]{i, j}, visited); } } return flag; } public void dfs(char[][] board, StringBuffer currentWord, String word, int[] index, int[][] visited){ if(flag){ return; } if(currentWord.length() == word.length()){ if(currentWord.toString().equals(word)){ flag = true; } return; } if(!(index[0] >= 0 && index[1] >= 0 && index[0] < board.length && index[1] < board[0].length && visited[index[0]][index[1]] == 0)){ return; } if(word.charAt(currentWord.length()) != board[index[0]][index[1]]){ return; } visited[index[0]][index[1]] = 1; int[][] next_index = new int[4][2]; next_index[0][0] = index[0] - 1; next_index[0][1] = index[1]; next_index[1][0] = index[0] + 1; next_index[1][1] = index[1]; next_index[2][0] = index[0]; next_index[2][1] = index[1] - 1; next_index[3][0] = index[0]; next_index[3][1] = index[1] + 1; for(int i = 0; i < next_index.length; i++){ // if(next_index[i][0] >= 0 && next_index[i][1] >= 0 && next_index[i][0] < board.length // && next_index[i][1] < board[0].length && visited[next_index[i][0]][next_index[i][1]] == 0){ currentWord.append(board[index[0]][index[1]]); dfs(board, currentWord, word, new int[]{next_index[i][0], next_index[i][1]}, visited); currentWord.deleteCharAt(currentWord.length() - 1); // } } visited[index[0]][index[1]] = 0; } }
再優化,發現currentWord的構造是沒有必要的,由於題目不是要求列出全部可能的word組合,這裏只須要判斷當前詞的長度是否是和要找的word同樣就能夠了,因此遞歸只須要一個記錄step的int就能夠。
第二,dfs方法當前座標的參數,二維數組徹底能夠換成兩個int參數,也更節省時間。
第三,dfs方法內下一狀態的二維數組next_index也能夠省去,由於它的大小是固定的,無非是上下左右四次。因此改成手動調用dfs。
第四,dfs內部每次得到board的長和寬board.length,都要花費必定時間,能夠預先把他們存入兩個變量rows和columns,這也可節省一點時間。
354-400ms。
public class Solution { boolean flag = false; public boolean exist(char[][] board, String word) { int[][] visited = new int[board.length][board[0].length]; for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ if(flag){ return flag; } dfs(board, word, 0, i, j, visited); } } return flag; } public void dfs(char[][] board, String word, int step, int x, int y, int[][] visited){ if(flag){ return; } if(step == word.length()){ flag = true; return; } int rows = board.length; int columns = board[0].length; if(x < 0 || x > rows - 1){ return; } if(y < 0 || y > columns - 1){ return; } if(word.charAt(step) != board[x][y]){ return; } if(visited[x][y] == 1){ return; } visited[x][y] = 1; // if(x - 1 >= 0 && visited[x - 1][y] == 0){ dfs(board, word, step + 1, x - 1, y, visited); // } // if(x + 1 < rows && visited[x + 1][y] == 0){ dfs(board, word, step + 1, x + 1, y, visited); // } // if(y - 1 >= 0 && visited[x][y - 1] == 0){ dfs(board, word, step + 1, x, y - 1, visited); // } // if(y + 1 < columns && visited[x][y + 1] == 0){ dfs(board, word, step + 1, x, y + 1, visited); // } visited[x][y] = 0; } }
最後的優化,省去Solution類的成員變量flag,將dfs的方法返回值改成boolean,這樣代碼更爲簡潔。花費基本都在350-360ms。
public class Solution { public boolean exist(char[][] board, String word) { int[][] visited = new int[board.length][board[0].length]; for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ if(dfs(board, word, 0, i, j, visited)){ return true; } } } return false; } public boolean dfs(char[][] board, String word, int step, int x, int y, int[][] visited){ int rows = board.length; int columns = board[0].length; if(step == word.length()){ return true; } if(x < 0 || x > rows - 1){ return false; } if(y < 0 || y > columns - 1){ return false; } if(word.charAt(step) != board[x][y]){ return false; } if(visited[x][y] == 1){ return false; } visited[x][y] = 1; boolean result = dfs(board, word, step + 1, x - 1, y, visited) || dfs(board, word, step + 1, x + 1, y, visited) || dfs(board, word, step + 1, x, y - 1, visited) || dfs(board, word, step + 1, x, y + 1, visited); visited[x][y] = 0; return result; } }
把剪枝的代碼多個if放在一個裏面,又能夠節省一些時間。花費312ms。
public class Solution { public boolean exist(char[][] board, String word) { int[][] visited = new int[board.length][board[0].length]; for(int i = 0; i < board.length; i++){ for(int j = 0; j < board[0].length; j++){ if(dfs(board, word, 0, i, j, visited)){ return true; } } } return false; } public boolean dfs(char[][] board, String word, int step, int x, int y, int[][] visited){ int rows = board.length; int columns = board[0].length; if(step == word.length()){ return true; } if(x < 0 || x > rows - 1 || y < 0 || y > columns - 1 || word.charAt(step) != board[x][y] || visited[x][y] == 1){ return false; } visited[x][y] = 1; boolean result = dfs(board, word, step + 1, x - 1, y, visited) || dfs(board, word, step + 1, x + 1, y, visited) || dfs(board, word, step + 1, x, y - 1, visited) || dfs(board, word, step + 1, x, y + 1, visited); visited[x][y] = 0; return result; } }
最後總結兩個問題:正確的剪枝和回溯。
剪枝應該是在遞歸進入後,不符合條件的首先return。而不是在進入下次遞歸的時候用if來判斷,這樣耗費過長時間。
回溯應該是回溯當前狀態,而不是即將處理的下一狀態,因此下面的回溯是錯誤的。
dfs(board, word, step + 1, x - 1, y, visited); visited[x - 1][y] = 0; dfs(board, word, step + 1, x + 1, y, visited); visited[x + 1][y] = 0; dfs(board, word, step + 1, x, y - 1, visited); visited[x][y - 1] = 0; dfs(board, word, step + 1, x, y + 1, visited); visited[x][y + 1] = 0;
總結一下這題避免超時(TLE)的方法。
1. 遞歸的剪枝。必定是放在遞歸方法的開始,不符合條件就return。而不是用if判斷什麼時候進入遞歸。
2. 不要再循環內部聲明很大的多維數組。
3. DFS內部省去沒必要要的操做,好比字符串鏈接。
4. DFS內部慎用循環,由於原本就遞歸了。
5. 少用數組做爲遞歸函數的參數傳遞。
6. 屢次if判斷若是返回同一個值,能夠放在一個if裏。
7. 屢次取得方法結果能夠放在一個變量裏。