题目描述
代码展示
思路一(超时)
看到这个题目的第一反应,我就想以单词为基础,在网格区域去找,找到了就加入结果当中,算是一种暴力的解法吧。首先以每个单词为基础去找,遍历每个网格,如果网格的字符与单词的首字符匹配,就DFS去深度遍历,DFS按照每个方向去找,这里找的时候要避免重复访问,要设定一个isvisit数组判断,直到找到目标单词为止。但是这种思路在力扣上面有个比较恶心的测试案例给卡住了,那个案例单词有很多,而且全是重复的前缀的字符串,比如“aaaaay”和“aaaaaz”这种,就导致这种方法重复搜索了很多,最后超时了,但是代码展示在下面
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class t211_1 {//以单词为基准,在网格中去搜索每个单词的暴力方法
HashSet<String> ret=new HashSet<>();//避免重复
List<String> res=new ArrayList<>();//结果
boolean[][] isvisit=new boolean[12][12];//判断是否重复搜索
int[][] dirs=new int[][]{{1,0},{-1,0},{0,1},{0,-1}};//方向
public List<String> findWords(char[][] board, String[] words) {
for (String word : words) {//按照每个单词搜索
for (int j = 0; j < board.length; j++) {
for (int k = 0; k < board[0].length; k++) {
if (board[j][k] == word.charAt(0)) {//网格字符与单词第一个字符匹配,就以该网格为基础,深度搜索
isvisit[j][k] = true;
dfs(board, word, j, k, 1);//深度搜索
isvisit[j][k] = false;
}
if (ret.contains(word)) break;//搜索成功,剪枝到下一个单词
}
if (ret.contains(word)) break;
}
}
res.addAll(ret);//转为List<String>
return res;
}
public void dfs(char[][] board,String word,int x,int y,int index){
if(index==word.length()){//成功找到
ret.add(word);
return;
}
for(int[] dir:dirs){//每个方向一次探索
int dx=x+dir[0],dy=y+dir[1];
//超出网格区域
if(dx<0||dx>=board.length||dy<0||dy>=board[0].length) continue;
//重复访问
if(isvisit[dx][dy]) continue;
//字符匹配
if(board[dx][dy]==word.charAt(index)){
index++;
isvisit[dx][dy]=true;
//继续深度搜索
dfs(board,word,dx,dy,index);
isvisit[dx][dy]=false;
index--;
}
}
}
}
测试案例如下(部分):
思路二(较慢)
第二种思路也是暴力算法的一种吧,就是以每个网格为基础,依次去深度遍历,把所有可能的单词全部找出来,然后看看是否和words数组里面的单词匹配。这种方法就可以避免上面那个思路的那个恶心测试案例,因为它是以网格数为基础的深度遍历,和单词的数量关系不大,也不会重复去遍历。具体的思路如下:
import java.util.*;
public class t211_2 {//将所有可能的连起来的串全部找到,看看符不符合要求,暴力算法
//放在外面避免写dfs的时候带在后面,是全局变量
Set<String> set = new HashSet<>();//放单词的
List<String> ans = new ArrayList<>();//放结果的
char[][] newboard;
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};//方向
int n, m;
boolean[][] vis = new boolean[12][12];//判断是否访问过,因为网格长度最大12
public List<String> findWords(char[][] board, String[] words) {
newboard = board;
m = newboard.length; n = newboard[0].length;
//初始化set
set.addAll(Arrays.asList(words));
//字符串变量
StringBuilder sb = new StringBuilder();
//每个店为起点
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
vis[i][j] = true;
sb.append(newboard[i][j]);
//深度遍历
dfs(i, j, sb);
vis[i][j] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
return ans;
}
void dfs(int i, int j, StringBuilder sb) {
//因为单词最长为10,超过10的不用考虑
if (sb.length() > 10) return ;
//找到了符合要求的单词
if (set.contains(sb.toString())) {
ans.add(sb.toString());
set.remove(sb.toString());
}
//每个方向一次遍历
for (int[] d : dirs) {
int dx = i + d[0], dy = j + d[1];
//超出边界
if (dx < 0 || dx >= m || dy < 0 || dy >= n) continue;
//重复访问
if (vis[dx][dy]) continue;
vis[dx][dy] = true;
sb.append(newboard[dx][dy]);
dfs(dx, dy, sb);
vis[dx][dy] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
}
思路三(DFS+回溯+字典树)
这里题目设定的意义就是字典树,因为他可以避免那个前缀相同的单词,避免重复遍历。力扣对字典树的解释是:前缀树(字典树)是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。前缀树可以用 O(∣S∣) 的时间复杂度完成如下操作,其中 ∣S∣ 是插入字符串或查询前缀的长度:
1.向前缀树中插入字符串 word;
2.查询前缀串 prefix 是否为已经插入到前缀树中的任意一个字符串 word 的前缀;
在初始化的时候,在字典树的根节点处设置好字符串变量s,表示按照之前的路径可以成功找到单词s,这也就是提前设定递归出口。然后按照网格里面的字符,依次深度遍历,在字典树中找,看看网格的字符是否与字典树的字符相匹配,匹配就继续深度遍历,否则就换方向或者返回。以上是大致思路,具体代码如下:
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class t211_3 {
//字典树
class TrieNode {
String s;//尾节点单词标记,递归出口
TrieNode[] tns = new TrieNode[26];//当前节点的子节点,因为字符就26个
}
//在字典树中创建一个单词
void insert(String s) {
TrieNode p = root;
for (int i = 0; i < s.length(); i++) {//每个字符一次遍历
//字符在子节点数组中的位置
int u = s.charAt(i) - 'a';
//不存在就新建
if (p.tns[u] == null) p.tns[u] = new TrieNode();
//依次递归创建
p = p.tns[u];
}
//尾节点更新单词
p.s = s;
}
Set<String> set = new HashSet<>();
char[][] newboard;
int n, m;
TrieNode root = new TrieNode();
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
boolean[][] vis = new boolean[12][12];
public List<String> findWords(char[][] board, String[] words) {
newboard = board;
m = newboard.length; n = newboard[0].length;
//初始化字典树
for (String w : words) insert(w);
//每个网格依次遍历
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
int u = newboard[i][j] - 'a';
//看看有没有以该网格字符为头字符的单词,有就深度遍历
if (root.tns[u] != null) {
vis[i][j] = true;
dfs(i, j, root.tns[u]);
vis[i][j] = false;
}
}
}
return new ArrayList<>(set);
}
//深度遍历
void dfs(int i, int j, TrieNode node) {
//到尾节点了,表示找到了该单词
if (node.s != null) set.add(node.s);
for (int[] d : dirs) {
int dx = i + d[0], dy = j + d[1];
if (dx < 0 || dx >= m || dy < 0 || dy >= n) continue;
if (vis[dx][dy]) continue;
//依次遍历,看看该方向的字符在不在字典树里面出现
int u = newboard[dx][dy] - 'a';
if (node.tns[u] != null) {
vis[dx][dy] = true;
dfs(dx, dy, node.tns[u]);
vis[dx][dy] = false;
}
}
}
}