阅读 155
leetcode每日一题系列-单词搜索II-「暴力DFS+回溯」+「Trie(字典树)+二维数组+回溯」-「Trie(字典树)+自定义结构体+回溯」

leetcode每日一题系列-单词搜索II-「暴力DFS+回溯」+「Trie(字典树)+二维数组+回溯」-「Trie(字典树)+自定义结构体+回溯」

leetcode-212-单词搜索II

[博客链接]

菜🐔的学习之路

掘金首页

[题目链接]

题目链接

[github地址]

github地址

[题目描述]

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words,找出所有同时在二维网格和字典中出现的单词。

单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

 

示例 1:

image.png

输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出:["eat","oath"]
复制代码

示例 2:

输入:board = [["a","b"],["c","d"]], words = ["abcb"]
输出:[]
复制代码

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 12
  • board[i][j] 是一个小写英文字母
  • 1 <= words.length <= 3 * 104
  • 1 <= words[i].length <= 10
  • words[i] 由小写英文字母组成
  • words 中的所有字符串互不相同

思路一:暴力法dfs+回溯

  • 最开始肯定可以暴力回溯拿到所有解
  • 将所有目标单词存入set
  • 然后每一次扫到目标单词,从set中删除一个元素
  • 通过vis记录每个字符是否被扫描,放置重复扫描
Set<String> set = new HashSet<>();
List<String> res = new ArrayList<>();
char[][] boards;
int m, n;
boolean[][] vis;

public List<String> findWords(char[][] board, String[] words) {
    m = board.length;
    n = board[0].length;
    boards = board;
    set.addAll(Arrays.asList(words));
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            vis = new boolean[m][n];
            char c = boards[i][j];
            vis[i][j] = true;
            StringBuilder temp = new StringBuilder();
            temp.append(c);
            dfs(i, j, temp);
            vis[i][j] = false;
            temp.deleteCharAt(temp.length() - 1);
        }
    }
    return res;
}

void dfs(int i, int j, StringBuilder sb) {

    if (sb.toString().length() > 10) {
        return;
    }
    if (set.contains(sb.toString())) {
        res.add(sb.toString());
        set.remove(sb.toString());
    }

    if (i > 0 && !vis[i - 1][j]) {
        vis[i - 1][j] = true;
        sb.append(boards[i - 1][j]);
        dfs(i - 1, j, sb);
        vis[i - 1][j] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (j > 0 && !vis[i][j - 1]) {
        sb.append(boards[i][j - 1]);
        vis[i][j - 1] = true;
        dfs(i, j - 1, sb);
        vis[i][j - 1] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (i < m - 1 && !vis[i + 1][j]) {
        sb.append(boards[i + 1][j]);
        vis[i + 1][j] = true;
        dfs(i + 1, j, sb);
        vis[i + 1][j] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (j < n - 1 && !vis[i][j + 1]) {
        sb.append(boards[i][j + 1]);
        vis[i][j + 1] = true;
        dfs(i, j + 1, sb);
        vis[i][j + 1] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
}
复制代码
  • 时间复杂度O(nm410n*m*4^{10})
  • 空间复杂度O(0n.length1m.lengthC\sum_0^{n.length-1}m.length*C)

思路二:Trie(字典树)+二维数组+回溯

  • 字典树可以理解为我们正常查询字典的流程化
  • 从第一个字节开始依次向下搜索
  • 字典树的数据结构有两种实现方法(常用的)二维数组以及自定义结构体
  • 具体可以参考三叶大佬的公众号
  • 虽然我不知道为啥二维数组TLE了但是我自己确实跑出来了很奇怪
class Trie {
    int[][] trie;
    int[] cnt;
    int idx;

    public Trie() {
        //26个字母
        trie = new int[1000009][26];
        cnt = new int[1000009];
        idx = 0;
    }

    //插入字符串
    public void insert(String s) {
        int p = 0;
        for (int i = 0; i < s.length(); i++) {
            int num = s.charAt(i) - 'a';
            //当前为初始字符则初始化
            if (trie[p][num] == 0) {
                trie[p][num] = ++idx;
            }
            //移动当前指针指向到下一个字符
            p = trie[p][num];
        }
        cnt[p]++;
    }

    //搜索字符串
    public boolean search(String s) {
        int p = 0;
        for (int i = 0; i < s.length(); i++) {
            int num = s.charAt(i) - 'a';
            if (trie[p][num] == 0) return false;
            p = trie[p][num];
        }
        return cnt[p]!=0;
    }

    public boolean startsWith(String s) {
        int p = 0;
        for (int i = 0; i < s.length(); i++) {
            int num = s.charAt(i) - 'a';
            if (trie[p][num] == 0) return false;
            p = trie[p][num];
        }
        return true;
    }
}
复制代码
Set<String> set = new HashSet<>();
char[][] boards;
int m, n;
boolean[][] vis = new boolean[15][15];
Trie trie = new Trie();

public List<String> findWords(char[][] board, String[] words) {
    boards = board;
    m = board.length;
    n = board[0].length;
    for (String word : words
    ) {
        trie.insert(word);
    }
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            int num = boards[i][j] - 'a';
            if (trie.trie[0][num] != 0) {
                vis[i][j] = true;
                StringBuilder sb = new StringBuilder();
                sb.append(boards[i][j]);
                dfs(i, j, sb);
                vis[i][j] = false;
            }
        }
    }
    List<String> res = new ArrayList<>(set);
    return res;
}

void dfs(int i, int j, StringBuilder sb) {
    if (sb.toString().length() > 10) {
        return;
    }
    if (trie.search(sb.toString())) {
        set.add(sb.toString());
    }
    if (i > 0 && !vis[i - 1][j]) {
        vis[i - 1][j] = true;
        sb.append(boards[i - 1][j]);
        dfs(i - 1, j, sb);
        vis[i - 1][j] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (j > 0 && !vis[i][j - 1]) {
        sb.append(boards[i][j - 1]);
        vis[i][j - 1] = true;
        dfs(i, j - 1, sb);
        vis[i][j - 1] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (i < m - 1 && !vis[i + 1][j]) {
        sb.append(boards[i + 1][j]);
        vis[i + 1][j] = true;
        dfs(i + 1, j, sb);
        vis[i + 1][j] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
    if (j < n - 1 && !vis[i][j + 1]) {
        sb.append(boards[i][j + 1]);
        vis[i][j + 1] = true;
        dfs(i, j + 1, sb);
        vis[i][j + 1] = false;
        sb.deleteCharAt(sb.length() - 1);
    }
}
复制代码
  • 时间复杂度O(nm410n*m*4^{10})
  • 空间复杂度O(0n.length1m.lengthC\sum_0^{n.length-1}m.length*C)

字典树+自定义结构体+回溯

  • 这种做法就和三叶的一模一样了
  • 但是我还是很好奇为啥第二种tle,希望有大佬可以解释一下
class Solution {

char[][] boards;
        int m, n;
        class Trie{
            Trie[] trs;
            String s;
            public Trie(){
                 trs = new Trie[26];
            }
        }
        Set<String> set = new HashSet<>();
        Trie trie = new Trie();
        public void insert(String s){
            Trie p = trie;
            for (int i = 0; i < s.length(); i++) {
                int u = s.charAt(i)-'a';
                if (p.trs[u]==null){
                    p.trs[u] = new Trie();
                }
                p = p.trs[u];
            }
            p.s = s;

        }
        int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
        boolean[][] vis = new boolean[15][15];
        public List<String> findWords(char[][] board, String[] words) {

            boards = board;
            m = board.length;
            n = board[0].length;
            for (String word : words
            ) {
                insert(word);
            }
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    int num = boards[i][j] - 'a';
                    if (this.trie.trs[num] != null) {
                        vis[i][j] = true;
                        dfs(i, j, this.trie.trs[num]);
                        vis[i][j] = false;
                    }
                }
            }
            List<String> res = new ArrayList<>(set);
            return res;
        }

        void dfs(int i, int j, Trie trie) {

            if (trie.s!=null){
                set.add(trie.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 = boards[dx][dy] - 'a';
                if (trie.trs[u] != null) {
                    vis[dx][dy] = true;
                    dfs(dx, dy, trie.trs[u]);
                    vis[dx][dy] = false;
                }
            }
        }

}
复制代码
  • 时间复杂度O(nm410n*m*4^{10})
  • 空间复杂度O(0n.length1m.lengthC\sum_0^{n.length-1}m.length*C)
文章分类
后端
文章标签