☆打卡算法☆LeetCode 212. 单词搜索 II 算法解析

284 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情

推荐阅读

大家好,我是小魔龙,Unity3D软件工程师,VR、AR,虚拟仿真方向,不定时更新软件开发技巧,生活感悟,觉得有用记得一键三连哦。

一、题目

1、算法题目

“给定一个二维字符网格和一个单词列表,返回二维网格中的所有单词。”

题目链接:

来源:力扣(LeetCode)

链接: 212. 单词搜索 II - 力扣(LeetCode)

2、题目描述

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。

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

image.png

示例 1:
输入: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"]
输出: []

二、解题

1、思路分析

题意要求给定一个二维字符网格和单词列表,返回二维网格中的所有单词。

遇到这种匹配单词的都可以试着使用字典树来解决,字典树是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。

字典树已经实现过很多次了,就不多说了,但是字典树只是搜索单词,而本题是要找出所有的单词,所以需要加一个回溯操作。

遍历二维网格中的所有单元格,深度优先搜索所有从当前单元格触发组成的路径。

如果当前路径是单词列表中的单词,就加入到结果集中。

如果当前路径是单词列表中的一个单词的前缀,则继续搜索,如果不是,就剪枝,这样就可以将单词列表中所有字符串先添加到前缀树中,而后用字典树来查询当前路径是否为单词列表中的任意一个单词的前缀了。

2、代码实现

代码参考:

class Solution {
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }

        Set<String> ans = new HashSet<String>();
        for (int i = 0; i < board.length; ++i) {
            for (int j = 0; j < board[0].length; ++j) {
                dfs(board, trie, i, j, ans);
            }
        }

        return new ArrayList<String>(ans);
    }

    public void dfs(char[][] board, Trie now, int i1, int j1, Set<String> ans) {
        if (!now.children.containsKey(board[i1][j1])) {
            return;
        }
        char ch = board[i1][j1];
        now = now.children.get(ch);
        if (!"".equals(now.word)) {
            ans.add(now.word);
        }

        board[i1][j1] = '#';
        for (int[] dir : dirs) {
            int i2 = i1 + dir[0], j2 = j1 + dir[1];
            if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
                dfs(board, now, i2, j2, ans);
            }
        }
        board[i1][j1] = ch;
    }
}

class Trie {
    String word;
    Map<Character, Trie> children;
    boolean isWord;

    public Trie() {
        this.word = "";
        this.children = new HashMap<Character, Trie>();
    }

    public void insert(String word) {
        Trie cur = this;
        for (int i = 0; i < word.length(); ++i) {
            char c = word.charAt(i);
            if (!cur.children.containsKey(c)) {
                cur.children.put(c, new Trie());
            }
            cur = cur.children.get(c);
        }
        cur.word = word;
    }
}

image.png

3、时间复杂度

时间复杂度:O(m x n x 3l-1)

其中m是二维网格的高度,n是二维网格的宽度,l是最长单词的长度,需要遍历m x n个单元格,每个单元格的最多需要遍历 4 x 3l-1条路径。

空间复杂度:O(k x l)

其中k是单词列表的长度,l是单词的长度,最坏情况下,需要O(k x l)用于存储字典树。

三、总结

在具体实现中:

  • 因为单词不能重复,所以需要哈希表进行去重
  • 在回溯过程中,不需要每一步都判断当前路径是否是单词列表中的单词前缀,只需要记录下路径中每个单元格所对应的前缀树节点,只需要判断新增的单元格是否是上一个单元格对应的前缀树的子节点即可。