「前端刷题」212.单词搜索 II(HARD)

145 阅读2分钟

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

题目(Word Search II)

链接:https://leetcode-cn.com/problems/word-search-ii
解决数:694
通过率:45.2%
标签:字典树 数组 字符串 回溯 矩阵 
相关公司:amazon uber microsoft 

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

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

 

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

 

提示:

  • 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 中的所有字符串互不相同

思路

  • 三部分:单词表 → 哈希表 → 前缀树。二维网络 → 递归 → 找前缀树中的单词。剪枝
  • 剪枝:pre存上节点len存分支数。从找到单词的节点向上找最后1个分支数为1分支,删除键名

代码

var findWords = function(board, words) {
    const h = {} // 前缀表
    const r = [] // 结果集数组

    /** 单词表 → 哈希表 → 前缀树 **/
    for (const w of words) {// 遍历单词表,用哈希表组装前缀树
        for(var i = 0, t = h, p; i < w.length; i++) {
            if (!t[w[i]]) {
                t[w[i]] = Object.create(null) // 创建一个不含_proto_的纯净对象作节点,比{}提升性能
                t[w[i]].len = 0 // 初始节点长度设为`0`,后面会`+1`
            }
            p = t // 存前缀树上一级节点
            t = t[w[i]] // 移到当前节点
            t.pre = p  // 当前节点的`pre`属性,指向上一级节点
            t.len++ // 单词共享前缀的情况,节点长度`+1`。在剪枝时,通过pre向上查找,直到len != 1,删除单词在前缀树的独占部分
        }
        t.word = w // 当前单词的前缀树最后一个字母,添加单词结尾标记,内容就是 单词本身
    }

    /** 剪枝 **/
    const s = (h, p) => {// 找到一个单词,就删掉 这个单词所在的前缀树,但不能影响其他单词,所以只删节点长度1(该单词独占)
        while(h.len === 1) p = h, h = h.pre // 从当前节点层层向上,直到节点长度不为1,存在分支
        for(var k in p) delete p[k]// p暂存最后一个节点长度为1的节点,把p从前缀树剪掉
    }

    /** 二维网络 → 递归 → 找前缀树中的单词 **/
    const d = (i, j, h) => { // 递归
        var t = board[i] && board[i][j] // 暂存单元格字母
        if (t === false || h[t] === undefined) return // 字母不存在(逾界) 或 字母不再前缀表
        h = h[t] // 字母在前缀表,向下找后面的节点
        if (h.word) { // 该字母是不是某个单词的结尾(组装前缀表时,把单词结尾标记 设为 单词本身)
            r.push(h.word) // 将单词放入结果集
            h.word = '' // 当单词结尾标记置空,避免这个单词被重复找到
            s(h) // 剪枝,传入前缀树当前节点
        }
        board[i][j] = '' // 当前格子找过了,值清空,继续递归不再找
        d(i - 1, j, h), d(i + 1, j, h), d(i, j - 1, h), d(i, j + 1, h) // 从当前格子开始,向四周继续递归
        board[i][j] = t // 回溯时,将当前格子值复原
    }
    for (let i = board.length; i--;) {// 遍历二维网格,将每个格子作为开始前缀,在前缀树找完整单词
        for (let j = board[0].length; j--;) {
            d(i, j, h)
        }
    }

    return r
};