• 算法-动态规划/trie树-单词拆分


    算法-动态规划/trie树-单词拆分

    1 题目概述

    1.1 题目出处

    https://leetcode.cn/problems/word-break/description/?envType=study-plan-v2&envId=top-interview-150

    1.2 题目描述

    在这里插入图片描述

    2 动态规划

    2.1 解题思路

    1. dp[i]表示[0, i)字符串可否构建
    2. 那么dp[i]可构建的条件是,[0,j)可构建且[j,i)包含在wordDict中
    3. 这里你可能会问,那如果是[j,i)不能直接构建,而是有wordDict种的两个单词构建怎么办?其实,因为我们是从低到高构建的动态规划,所以设k > j 且 k

    2.2 代码

    class Solution {
        public boolean wordBreak(String s, List<String> wordDict) {
            // dp[i]表示[0, i)字符串可否构建
            // 那么dp[i]可构建的条件是,[0,j)可构建且[j,i)包含在wordDict中
            boolean[] dp = new boolean[s.length() + 1];
            dp[0] = true;
            Set<String> set = new HashSet<>(wordDict);
            
            for (int i = 1; i <= s.length(); i++) {
                for (int j = 0; j < i; j++) {
                    if (dp[j] == true && set.contains(s.substring(j, i))) {
                        dp[i] = true;
                        break;
                    }
                }
            }
            return dp[s.length()];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.3 时间复杂度

    O(c*s.length)
    在这里插入图片描述

    2.4 空间复杂度

    O( s.length)

    3 trie树

    3.1 解题思路

    1. 将wordDict构建trie树
    2. 将s从位置0开始往后匹配查找
    3. 如果当前位置能匹配上,继续判断是否是单词结尾,如果是且下一个单词开始的匹配也能成功,就说明能构建,返回true
    4. 其他情况继续往后匹配

    3.2 代码

    class Solution {
        Trie root = new Trie();
        // 记忆数组,用来快速判定该位置是否可以作为单词结尾进行拆分构建
        boolean[] no = new boolean[300];
        public boolean wordBreak(String s, List<String> wordDict) {
            // 将所有word插入字典树
            for (String word : wordDict)
                root.insert(word);
    
            // 从0个字符开始往后查找,只要匹配成功说明可以构建目标字符串
            if (root.find(s, 0)) {
                return true;
            }
            return false;
        }
    
        class Trie{
            public Trie[] children = new Trie[26];
            // 当前child代表的字符是否是单词结尾
            boolean isEnd = false;
            public void insert(String word) {
                if (null == word || word.length() == 0) {
                    isEnd = true;
                    return;
                }
                int index = word.charAt(0) - 'a';
                Trie child = children[index];
                if (null == child) {
                    child = new Trie();
                    children[index] = child;
                }
                child.insert(word.substring(1));
            }
            public boolean find(String s, int i) {
                    // 快速判定当前字符位置是否可以拆分构建
                    // 注意这里必须判定当前节点是否是root,因为我们缓存是从根节点开始的
                    // 否则会对其他child的正常判断过程造成误判
                    if (this == root && no[i]) {
                        return false;
                    }
                    char firstC = s.charAt(i);
                    Trie child = children[firstC - 'a'];
                    if (null == child) {
                        // 如果不能匹配指定位置字符,肯定不可构建
                        if (this == root) {
                            no[i] = true;
                        }
                        return false;
                    }
                    if (child.isEnd) {
                        // 如果能找到目标字符,且字符是单词结尾
                        if (i + 1 == s.length()) {
                            // 如果
                            // 1.已经扫描到字符串最后的字符
                            // 就说明当前位置可以用来拆分构建目标字符串
                            return true;
                        } else {
                            if (root.find(s, i+1)) {
                                // 如果下一个字符往后的字符串能构建
                                // 就说明当前位置可以用来拆分构建目标字符串
                                return true;
                            } else {
                                // 否则说明i+1字符虽是单词结尾,但无法直接拆分构建,记录下来
                                no[i+1] = true;
                            }
                        }
                    }
    
                    if (i + 1 < s.length()) {
                        // 还未到结尾,可以继续往后查找
                        return child.find(s, i+1);
                    } else {
                        // 已到单词结尾,构建失败
                        return false;
                    }
                }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    3.3 时间复杂度

    在这里插入图片描述

    3.4 空间复杂度

    O(s.length)

    参考

  • 相关阅读:
    [Day 10] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
    Android 导入导出excel xls、xlsx
    工业标准半导体SECS标准低频RFID读写器JY-V640性能与应用方案
    leetcode:交叉链表
    安装gymnasium[box2d]的问题
    elementui el-table在有summary-method时,table数据行将合计行遮挡住了
    算法的时间复杂度
    python中类与对象的命名空间(静态属性的陷阱)、__dict__ 和 dir() 在继承中使用说明
    JS数据结构与算法-队列结构
    从零开始的Docker Desktop使用,Docker快速上手 ( ̄︶ ̄) Docker介绍和基础使用
  • 原文地址:https://blog.csdn.net/baichoufei90/article/details/133459796