LeetCode第[79]题(Java):Word Search(矩阵单词搜索)
题目:矩阵单词搜索
难度:Medium
题目内容:
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.
翻译:
给定一个2D的字符数组和一个单词,找出这个词是否存在于网格中。
这个词可以用顺序相邻的细胞的字母来构造,在那里“相邻”的细胞是水平的或垂直的相邻的。同一个字母单元可能不止一次使用。
Example:
board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ] Given word = "ABCCED", return true. Given word = "SEE", return true. Given word = "ABCB", return false.
我的思路:对整个二维数组进行循环,对于每一个字母都进入递归方法,
1、判断当前字符如果不与传入字符串第一个字母匹配,或者访问过则返回fasle;
2、这是最后一个字符,返回true;(已经通过上面的判断已经是匹配的)
3、将此位置的访问符置1,然后分别向上、下、左、右四个方向调用递归方法;
4、四个方向访问完毕,说明由此处开始的路径已经有结果,将此位置访问符再置0,并返回四个方向的“或”。
为什么又要再置0?
因为整个递归过程就只有一个used访问标志矩阵(引用类型),如果每次访问后就设置1,不再管,那么之前已经失败的路径就会对之后再访问的路径(对于此路径是新访问的)产生影响。
此处的访问符类 used 似于第[47]题Permutations 2 中的used使用方法是一样的,不过意义不同,
所以,若在递归中有包含访问符,调用递归结束后,访问符应该置0(或false)
我的代码:
1 public boolean exist(char[][] board, String word) { 2 for (int i = 0; i < board.length; i++) { 3 for (int j = 0; j < board[0].length; j++) { 4 if (find(word, i, j, board, new int[board.length][board[0].length])) { 5 return true; 6 } 7 } 8 } 9 return false; 10 } 11 12 private boolean find(String word, int i, int j, char[][] board, int[][] used) { 13 if (i < 0 || i > board.length-1 || j < 0 || j > board[0].length-1) { 14 return false; 15 } 16 if (board[i][j] != word.charAt(0) || used[i][j] == 1) { 17 return false; 18 } else if (word.length() == 1) { 19 return true; 20 } 21 used[i][j] = 1; 22 boolean ans = find(word.substring(1), i-1, j, board, used) 23 || find(word.substring(1), i+1, j, board, used) 24 || find(word.substring(1), i, j-1, board, used) 25 || find(word.substring(1), i, j+1, board, used); 26 used[i][j] = 0; 27 return ans; 28 }
我的复杂度:O((m*n)2)
编码过程中的问题:
1、之前采用的是先判断首字母是匹配然后直接返回find方法的结果,后来发现这样做是不行的,因为如果在正确答案的前面如果有一个是前面匹配后面不匹配的错误答案,就会直接返回错误答案的false;——————如果 true 则return true,否则继续
2、之前没考虑到标志位,从而路径会往回找;“【【a,a】】” “aaa”
3、之前是用了四个标志位“up-down-left-right”,每次都判断是否能上下左右再进行递归调用,如下:
if (i > 0) { up = find(word.substring(1), i-1, j, board, used); }
然后最后再对这四个标志位进行 或 运算,但是这样做路径中的字母每次都会进行四个递归,没有短路的可能,所以当需要方法多分支递归的时候,最好改成直接调用方法进行 短路与或 运算, 然后在方法的开头加入此结果的判断,这样能减少不少多余的运算。【本题将是否能上下左右的判断加入到了递归方法的最开始】
4、本方法其实还可以优化,就是将访问过的字符用“*”表示,在递归结束后再设置成原来的值,这样就可以省去新建一个标志矩阵。
答案代码:
1 public boolean exist(char[][] board, String word) { 2 char[] w = word.toCharArray(); 3 for (int y=0; y<board.length; y++) { 4 for (int x=0; x<board[y].length; x++) { 5 if (exist(board, y, x, w, 0)) return true; 6 } 7 } 8 return false; 9 } 10 11 private boolean exist(char[][] board, int y, int x, char[] word, int i) { 12 if (i == word.length) return true; 13 if (y<0 || x<0 || y == board.length || x == board[y].length) return false; 14 if (board[y][x] != word[i]) return false; 15 board[y][x] ^= 256; 16 boolean exist = exist(board, y, x+1, word, i+1) 17 || exist(board, y, x-1, word, i+1) 18 || exist(board, y+1, x, word, i+1) 19 || exist(board, y-1, x, word, i+1); 20 board[y][x] ^= 256; 21 return exist; 22 }
答案思路:
基本思路是一样的,不过有两个亮点是比我的方法好的:
1、利用char[] 和 一个 int 表示当前所在第几个字母,代替了频繁利用subString来新建一个字符串;
2、利用二进制运算 与或 的特性——与或全1,再与或全1就会等于自己,因为说好都是字母,而且字母为65~90,95~122内,所以与或128也是可以的。从而不必再用temp了。