在问 · 算法题解

212. 单词搜索 II

题目描述

输入一个 m x n 的字母网格 board 和一个单词列表 words。返回 words 中所有能在网格里拼出来的单词,顺序任意。

拼一个单词时,从某个格子出发,每次走到上下左右相邻的格子,依次经过的字母要正好组成这个单词。在同一个单词里,每个格子最多用一次。

示例 1
输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]]
words = ["oath","pea","eat","rain"]
输出:["oath","eat"]

解释:"oath" 和 "eat" 都能沿相邻格子拼出,"pea" 和 "rain" 不能。

示例 2
输入:board = [["a","b"],["c","d"]], words = ["abcb"]
输出:[]

解释:拼 "abcb" 需要重复使用写着 b 的格子,不允许。

数据范围

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 12
  • board[i][j] 是小写英文字母。
  • 1 <= words.length <= 3 * 10^4
  • 1 <= words[i].length <= 10
  • words[i] 只含小写英文字母。
  • words 中的字符串两两不同。

解题思路

先把所有单词放进字典树(Trie),单词结尾节点用 $ 记下整个单词。然后从每个格子出发做回溯:当前字符不是 Trie 节点的孩子就立刻返回;否则走到孩子节点,若它有 $ 就记录单词并清空(避免重复),再把格子标成 # 向四个方向搜索,最后恢复格子。一次搜索同时匹配所有共享前缀的单词。

为什么正确

dfs(r, c, node) 时,当前路径上的字符恰好是从 Trie 根走到 node 的前缀,路径上的格子都被标成了 #。

复杂度分析

最坏 O(m·n·4·3^(L-1)),L 为最长单词长度;Trie 空间为所有单词长度之和。

易错点

找到单词后要清空节点的 $,否则同一个单词从不同起点被找到多次会重复加入答案;回溯返回前一定要恢复 board[r][c]。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        root = {}
        for w in words:
            node = root
            for ch in w:
                node = node.setdefault(ch, {})
            node['$'] = w
        m, n = len(board), len(board[0])
        res = []

        def dfs(r, c, node):
            ch = board[r][c]
            if ch not in node:
                return
            nxt = node[ch]
            if nxt.get('$'):
                res.append(nxt['$'])
                nxt['$'] = None
            board[r][c] = '#'
            for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
                nr, nc = r + dr, c + dc
                if 0 <= nr < m and 0 <= nc < n:
                    dfs(nr, nc, nxt)
            board[r][c] = ch

        for r in range(m):
            for c in range(n):
                dfs(r, c, root)
        return res

载入编辑器,自己跑一遍 →

回溯 专题的其他题目

查看 回溯 全部题目 →