Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

https://leetcode.com/problems/word-search/

Given a 2D board and a word, find if the word exists in the grid.

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.

解题思路:

这道题也属于DFS,可以化解为从matrix中任意点开始往下拓展的状态中,可以达到word吗?下一状态的可能性就是当前index的上下左右。但是超时是一个大问题,下面就是开始模仿前面DFS写的代码,本身有不少问题,但是放在IDE里debug,是能得出正确答案的。不过TLE,百思不得其解

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的,做了两个修改。

第一,将第一处注释的visited二维数组放到了最外面,因为最后会回溯,所以不必要每次开始都划分一个新的数组,而声明一个如此大的二维数组是很花时间的。

第二,在第二处注释的判断放在了递归函数的最前面。这样使得递归的开始条件不合适就直接return,而不是在进入递归前就判断是否要进入。注意,这才是做DFS或者递归剪枝的正确方法。

下面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. 多次取得方法结果可以放在一个变量里。

posted on 2015-03-11 22:40  NickyYe  阅读(318)  评论(0编辑  收藏  举报