leetcode 212. Word Search II(单词搜索 II)

本文介绍了一种利用前缀树优化2D板单词搜索的方法,通过对所有目标单词进行前缀匹配达到剪枝的目的,显著提高了算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给出一个2D板和字典中的单词列表,找出所有同时在2D板和字典中出现的单词。

每个单词必须由顺序相邻单元的字母构成,其中“相邻”单元是那些水平或垂直相邻的单元。同一个字母单元在一个单词中可能不会多次使用。

例如, 给出 words = [“oath”,”pea”,”eat”,”rain”] 和 board =

[
[‘o’,’a’,’a’,’n’],
[‘e’,’t’,’a’,’e’],
[‘i’,’h’,’k’,’r’],
[‘i’,’f’,’l’,’v’]
]
返回 [“eat”,”oath”]。

注意: 您可以假设所有输入都由小写字母 a-z 组成。

你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?

如果当前单词不存在所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? Trie(前缀树) 如何?如果你想学习如何实现一个基本的前缀树,请先处理这个问题: 实施Trie(前缀树)。

这道题目最容易想到的就是穷举法:
找到所有匹配目标字符串的首字母,然后往相邻位置寻找,寻找的时候可以在2D板中把已经找到的字符标记,避免重复使用同一位置字符。

List<String> list = new ArrayList<String>();
    public List<String> findWords(char[][] board, String[] words) {
        list = new ArrayList<String>();

        if(board.length > 0 && board[0].length > 0 && words.length > 0){
            for(int i=0;i<board.length;i++){
                for(int j=0;j<board[0].length;j++){
                    for(int k=0;k<words.length;k++){
                        if(board[i][j] == words[k].charAt(0)){
                            char c = board[i][j];
                            board[i][j] = '1';
                            findWords2(board,i,j, words[k] , words[k].substring(1));
                            board[i][j] = c;
                        }
                    }

                }
            }

            return list;
        }
        return list;

    }
    public boolean findWords2(char[][] board,int i,int j, String word,String remain) {
        if(remain.equals("") ){
            if(!list.contains(word)){
                list.add(word);
            }
            return true;
        }
        if(i-1 > -1 && board[i-1][j] != '1' && board[i-1][j] == remain.charAt(0)){
            char c = board[i-1][j];
            board[i-1][j] = '1';
            if(findWords2(board,i-1, j, word, remain.substring(1))){
                board[i-1][j] = c;
                return true;
            }
            board[i-1][j] = c;
        }
        if(i+1 < board.length && board[i+1][j] != '1' && board[i+1][j] == remain.charAt(0)){
            char c = board[i+1][j];
            board[i+1][j] = '1';
            if(findWords2(board,i+1, j, word, remain.substring(1))){
                board[i+1][j] = c;
                return true;
            }
            board[i+1][j] = c;
        }
        if(j-1 > -1 && board[i][j-1] != '1' && board[i][j-1] == remain.charAt(0)){
            char c = board[i][j-1];
            board[i][j-1] = '1';
            if(findWords2(board,i, j-1, word, remain.substring(1))){
                board[i][j-1] = c;
                return true;
            }
            board[i][j-1] = c;
        }
        if(j+1 < board[0].length && board[i][j+1] != '1' && board[i][j+1] == remain.charAt(0)){
            char c = board[i][j+1];
            board[i][j+1] = '1';
            if(findWords2(board,i, j+1, word, remain.substring(1))){
                board[i][j+1] = c;
                return true;
            }
            board[i][j+1] = c;
        }
        return false;
    }

逻辑比较简单,但是效率不高,806 ms。

  1. 提示中提到了前缀树,那么可以考虑先实现前缀树,再通过前缀匹配达到剪枝目的。
  2. 这么做的好处是一次把所有目标单词纳入树中,回溯时可以与所有目标单词做匹配。
  3. 关于前缀树的实现也比较简单,由于限制值出现小写字母,那么用一颗每个节点最多26个子节点的树来实现,需要注意的地方是每次insert完成时应该在当前节点标记一下isend,表示此整个单词在树中,到时匹配单词的search()需要用到
  4. 而insert和startsWith只需要遍历trie就好了,差别只在于insert边遍历边插入
class Solution {
    class TrieNode{  
        TrieNode next[] = new TrieNode[26];
        public boolean isEnd;
        public TrieNode(){}  

    }  
    class Trie {
        private TrieNode head;

        /** Initialize your data structure here. */
        public Trie() {
            head = new TrieNode();
        }
        public boolean search(String word) {
            if(word == null || word.length() < 1){
                return true;    
            }
            if(head.next[word.charAt(0)-'a'] == null){
                return false;
            }
            TrieNode cur = head.next[word.charAt(0)-'a'];
            for(int i=1;i<word.length();i++){
                if(cur.next[word.charAt(i)-'a'] == null){
                    return false; 
                }
                cur = cur.next[word.charAt(i)-'a'];
            }
            if(cur.isEnd){
                return true;
            }
            return false; 
        }
        /** Inserts a word into the trie. */
        public void insert(String word) {
            if(word != null && word.length() > 0 && head.next[word.charAt(0)-'a'] == null){
                head.next[word.charAt(0)-'a'] = new TrieNode();
            }
            TrieNode cur = head.next[word.charAt(0)-'a'];
            for(int i=1;i<word.length();i++){
                if(cur.next[word.charAt(i)-'a'] == null){
                    cur.next[word.charAt(i)-'a'] = new TrieNode(); 
                }
                cur = cur.next[word.charAt(i)-'a'];
            }
            cur.isEnd = true;
        }

        /** Returns if there is any word in the trie that starts with the given prefix. */
        public boolean startsWith(String prefix) {
            if(prefix == null || prefix.length() < 1){
                return true;    
            }
            if(head.next[prefix.charAt(0)-'a'] == null){
                return false;
            }
            TrieNode cur = head.next[prefix.charAt(0)-'a'];
            for(int i=1;i<prefix.length();i++){
                if(cur.next[prefix.charAt(i)-'a'] == null){
                    return false; 
                }
                cur = cur.next[prefix.charAt(i)-'a'];
            }
            return true;
        }
    }


    Set<String> set = new HashSet<String>();
    public List<String> findWords(char[][] board, String[] words) {
        set.clear();
        List<String> resList = new ArrayList<String>();
        if(board.length > 0 && board[0].length > 0 && words.length > 0){
            Trie t = new Trie();
            for(int i=0;i<words.length;i++){
                t.insert(words[i]);
            }
            for(int i=0;i<board.length;i++){
                for(int j=0;j<board[0].length;j++){
                    char c = board[i][j];
                    board[i][j] = '1';
                    findWords2(board,i,j, t, c+"");
                    board[i][j] = c;
                }
            }

            for(int i=0;i<words.length;i++){
                if(set.contains(words[i]) && !resList.contains(words[i])){
                    resList.add(words[i]);
                }
            }

            return resList;
        }
        return resList;

    }
    public void findWords2(char[][] board,int i,int j,Trie t,String curStr) {
        if(t.search(curStr)){
            set.add(curStr);
        }
        if(t.startsWith(curStr)){
            if(i-1 > -1 && board[i-1][j] != '1'){
                char c = board[i-1][j];
                board[i-1][j] = '1';
                findWords2(board,i-1, j, t, curStr+c);
                board[i-1][j] = c;
            }
            if(i+1 < board.length && board[i+1][j] != '1'){
                char c = board[i+1][j];
                board[i+1][j] = '1';
                findWords2(board,i+1, j, t, curStr+c);
                board[i+1][j] = c;
            }
            if(j-1 > -1 && board[i][j-1] != '1'){
                char c = board[i][j-1];
                board[i][j-1] = '1';
                findWords2(board,i, j-1, t, curStr+c);
                board[i][j-1] = c;
            }
            if(j+1 < board[0].length && board[i][j+1] != '1'){
                char c = board[i][j+1];
                board[i][j+1] = '1';
                findWords2(board,i, j+1, t, curStr+c);
                board[i][j+1] = c;
            }
        }
        return ;
    }
}

用前缀树直接将用时减少到71ms

### LeetCode Hot 100 Problems 列表 LeetCode 的热门题目列表通常由社区投票选出,涵盖了各种难度级别的经典编程挑战。这些题目对于准备技术面试非常有帮助。以下是部分 LeetCode 热门 100 题目列表: #### 数组与字符串 1. **两数之和 (Two Sum)** 2. **三数之和 (3Sum)** 3. **无重复字符的最长子串 (Longest Substring Without Repeating Characters)** 4. **寻找两个正序数组的中位数 (Median of Two Sorted Arrays)** #### 动态规划 5. **爬楼梯 (Climbing Stairs)** 6. **不同的二叉搜索(Unique Binary Search Trees)** 7. **最大子序列和 (Maximum Subarray)** #### 字符串处理 8. **有效的括号 (Valid Parentheses)** 9. **最小覆盖子串 (Minimum Window Substring)** 10. **字母异位词分组 (Group Anagrams)** #### 图论 11. **岛屿数量 (Number of Islands)** 12. **课程表 II (Course Schedule II)** #### 排序与查找 13. **最接近原点的 K 个点 (K Closest Points to Origin)** 14. **接雨水 (Trapping Rain Water)** 15. **最长连续序列 (Longest Consecutive Sequence)[^2]** #### 堆栈与队列 16. **每日温度 (Daily Temperatures)** 17. **滑动窗口最大值 (Sliding Window Maximum)** #### 树结构 18. **验证二叉搜索(Validate Binary Search Tree)** 19. **二叉树的最大路径和 (Binary Tree Maximum Path Sum)** 20. **从前序与中序遍历序列构造二叉树 (Construct Binary Tree from Preorder and Inorder Traversal)** #### 并查集 21. **冗余连接 II (Redundant Connection II)** #### 贪心算法 22. **跳跃游戏 (Jump Game)** 23. **分割等和子集 (Partition Equal Subset Sum)** #### 双指针技巧 24. **环形链表 II (Linked List Cycle II)[^1]** 25. **相交链表 (Intersection of Two Linked Lists)** #### 其他重要题目 26. **LRU缓存机制 (LRU Cache)** 27. **打家劫舍系列 (House Robber I & II)** 28. **编辑距离 (Edit Distance)** 29. **单词拆分 (Word Break)** 此列表并非官方发布版本而是基于社区反馈整理而成。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值