【LeetCode】单词搜索 II [H](前缀树)

166 篇文章 0 订阅
78 篇文章 31 订阅

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

一、题目

给定一个 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 中的所有字符串互不相同

二、代码

class Solution {
    public List<String> findWords(char[][] board, String[] words) {
        // 记录前缀树中的所有字符串,做去重
        HashSet<String> trieSet = new HashSet<>();
        // 前缀树的根节点
        TrieNode head = new TrieNode();
        // 利用单词表构造前缀树
        for (String word : words) {
            // 相同的单词就去重
            if (!trieSet.contains(word)) {
                addTrieNode(head, word);
                trieSet.add(word);
            }
        }

        // 记录递归轨迹中走过的字符
        LinkedList<Character> path = new LinkedList<>();
        // 要返回的答案
        List<String> ans = new ArrayList<>();
        // 尝试以每一个位置作为起始点,看看能不能找到一个单词能和前缀树中的单词匹配上
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                process(board, i, j, head, path, ans);
            }
        }

        return ans;
    }
    
    public int process(char[][] board, int row, int col, TrieNode node, LinkedList<Character> path, List<String> ans) {
        // 如果矩阵中该位置为0,说明该位置已经走过了,不要重复走了,直接返回0
        if (board[row][col] == 0 ) {
            return 0;
        }

        char cha = board[row][col];
        // 如果当前矩阵中的字符在前缀树中没有相应的路线,也返回0
        if (node.next[cha - 'a'] == null || node.next[cha - 'a'].pass == 0) {
            return 0;
        }

        TrieNode next = node.next[cha - 'a'];
        board[row][col] = 0;
        path.addLast(cha);
        int cnt = 0;

        // 如果来到了一个单词的结尾字符,就说明找到了一个单词,将该单词加入到ans中
        if (next.end == true) {
            ans.add(charListToString(path));
            next.end = false;
            cnt++;
        }

        // 开始尝试四个方向,并且需要保证不越界
        if (row + 1 < board.length) {
            //node.pass--;
            cnt += process(board, row + 1, col, next, path, ans);
        }

        if (row - 1 >= 0) {
          
            //node.pass--;
            cnt += process(board, row - 1, col, next, path, ans);
            
        }

        if (col + 1 < board[0].length) {
            //node.pass--;
            cnt += process(board, row, col + 1, next, path, ans);
                
        }

        if (col - 1 >= 0) {
            //node.pass--;
            cnt += process(board, row, col - 1, next, path, ans);
        }

        // 恢复现场
        board[row][col] = cha;
        path.pollLast();
        next.pass -= cnt;
        return cnt;
    }

    // 将字符List转换为String
    public String charListToString(LinkedList<Character> path) {
        StringBuilder sb = new StringBuilder();

        for (Character c : path) {
            sb.append(c);
        }

        return sb.toString();
    }

    // 前缀树节点类
    class TrieNode {
        public TrieNode[] next;
        // 记录该节点被不同单词通过的次数
        public int pass;
        // 该节点是否为单词结束位置
        public boolean end;

        public TrieNode() {
            next = new TrieNode[26];
            pass = 0;
            end = false;
        }
    }

    // 将word加入前缀树
    public void addTrieNode(TrieNode head, String word) {
        char[] w = word.toCharArray();

        TrieNode node = head;
        node.pass++;
        for (int i = 0; i < w.length; i++) {
            if (node.next[w[i] - 'a'] == null) {
                node.next[w[i] - 'a'] = new TrieNode();
            }
            // node向下移动一个位置
            node = node.next[w[i] - 'a'];
            // 将下面的node的pass也加1
            node.pass++;
            
        }
        // 标记单词结尾节点
        node.end = true;
    }
}

三、解题思路 

尝试以矩阵中每个点作为出发点,收集单词。先将单词表中的所有单词建成前缀树,这样可以加快匹配速度。只要是涉及到字符串匹配的,马上要想到可以利用前缀树优化。

来到某一个(i,j)位置的字符a,先看前缀树的头节点的直接子路线有没有a,发现有a,那么就说明可以从a开始找。如果没有a就直接跳过,去尝试以下一个位置的字符作为开始点查找。

然后从a开始可以往上下左右4个方向走,至于到底有没有必要往某个方向走,也可以用前缀树来指导。例如我们如果发现a的直接子路线没有a在矩阵中上下左右的字符,那么就没有方向可以走。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值