ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Hard | LeetCode 212. 单词搜索 II | 回溯 + 前缀树

2021-05-16 16:32:16  阅读:265  来源: 互联网

标签:单词 212 TrieNode Hard II board word 节点 row


212. 单词搜索 II

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

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

示例 1:

img
输入: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:

img
输入: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搜索题目类似。

比如 Medium | LeetCode 200. 岛屿数量 | 矩阵 + DFS 这道题DFS的条件是, 下一个节点必须是1(陆地)
比如 Hard | LeetCode 329. 矩阵中的最长递增路径 | 矩阵+DFS , 这道题DFS的条件是, 下一个节点值必须大于当前节点值

而在本题当中, DFS的条件是 当前节点在某个单词的路径上。

解决这个的思路可以是把从DFS起点到当前的所有字符取出来, 然后判断是否是单词列表的某个单词的前缀。这种方法可行, 但是复杂度很高。

有一种优化的方法:前缀树 (Medium | LeetCode 208. 实现 Trie (前缀树) | 设计)

在DFS的过程当中, 让矩阵当前位置 和 前缀树的节点 对应起来, 就很容易知道 下一个节点是否在某个单词的路径上。当DFS到矩阵的某个位置, 其对应的在前缀树的节点 是代表一个单词的时候, 就将其加入到结果集当中。

同时在将某个单词添加进结果集的时候, 可以将前缀树的word标志置为null, 因为这个单词已经加进去了, 不会再添加了。
如果这个被添加进结果集的单词的节点是叶节点的时候, 可以删除这个单词的路径上的节点。这样可以提高效率。

代码

前缀树的数据结构编码

static class TrieTree {

    static class TrieNode {
        // 用来保存当前节点的所有孩子节点的字符以及节点映射信息
        private HashMap<Character, TrieNode> children = new HashMap<>();
        // 用来表示当前节点是否代表一个单词
        private String word;

        public void setWord(String word) {
            this.word = word;
        }

        private boolean containCharChild(Character c) {
            return children.containsKey(c);
        }

        private TrieNode getCharChild(Character c) {
            return children.get(c);
        }

        private TrieNode addCharChild(Character c) {
            TrieNode rtn  = new TrieNode();
            children.put(c, rtn);
            return rtn;
        }
    }

    private TrieNode root;

    public TrieTree() {
        this.root = new TrieNode();
    }

    public TrieNode getRoot() {
        return root;
    }

    public void addWord(String word) {
        char[] wordArray = word.toCharArray();
        TrieNode cur = root;
        for (char c : wordArray) {
            if (cur.containCharChild(c)) {
                cur = cur.getCharChild(c);
            } else {
                cur = cur.addCharChild(c);
            }
        }
        cur.setWord(word);
    }

    public void removeWord(String word) {

    }

    public void show() {
        dfsShowCore(root);
    }

    private void dfsShowCore(TrieNode parents) {
        if (parents.children.isEmpty()) {
            System.out.println();
        }
        for (Entry<Character, TrieNode> trieNodeEntry : parents.children.entrySet()) {
            TrieNode node = trieNodeEntry.getValue();
            System.out.print(trieNodeEntry.getKey());
            if (node.word != null) {
                System.out.print(String.format(" (%s)", node.word));
            }
            if (!node.children.isEmpty()) {
                System.out.print(" -> ");
            }
            dfsShowCore(node);
        }
    }
}

代码主体部分

public List<String> findWords(char[][] board, String[] words) {
    // 先根据单词列表构造前缀树
    TrieTree trie = new TrieTree();
    for (String word : words) {
        trie.addWord(word);
    }
    trie.show();
    TrieNode root = trie.getRoot();
    List<String> res = new ArrayList<>();
    for (int row = 0; row < board.length; ++row) {
        for (int col = 0; col < board[row].length; ++col) {
            if (root.containCharChild(board[row][col])) {
                backtracking(board, row, col, root, res);
            }
        }
    }
    return res;
}

private void backtracking(char[][] board, int row, int col, TrieNode parent, List<String> res) {
    Character letter = board[row][col];
    TrieNode curNode = parent.getCharChild(letter);

    // 找到一个单词
    if (curNode.word != null) {
        res.add(curNode.word);
        // 这个单词已经加进结果集了, 不会添加重复的单词, 所以将当前节点代表的单词删除
        curNode.word = null;
    }

    // 保证每一次枚举过程, 这个位置的字符不会重复使用, 使用"#"号标记, 这轮枚举结束之后回溯时, 会将其还原。
    board[row][col] = '#';

    // DFS遍历其他相邻的位置
    int[] rowOffset = {-1, 0, 1, 0};
    int[] colOffset = {0, 1, 0, -1};
    for (int i = 0; i < 4; ++i) {
        int newRow = row + rowOffset[i];
        int newCol = col + colOffset[i];
        if (newRow < 0 || newRow >= board.length || newCol < 0
                || newCol >= board[0].length) {
            continue;
        }
        if (curNode.containCharChild(board[newRow][newCol])) {
            backtracking(board, newRow, newCol, curNode, res);
        }
    }

    // 还原初始字母
    board[row][col] = letter;

    // 对于 Trie 中的叶节点,一旦遍历它(即找到匹配的单词),就不需要再遍历它了, 所以可以直接删除
    // 如果删除某个不是单词的叶节点, 那么遍历到这个叶节点也没有意义了, 同样删除掉。
    if (curNode.children.isEmpty()) {
        parent.children.remove(letter);
    }
}

标签:单词,212,TrieNode,Hard,II,board,word,节点,row
来源: https://www.cnblogs.com/chenrj97/p/14774021.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有