LeetCode 👉 HOT 100 👉 单词拆分 - 中等题

660 阅读2分钟

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。

题目

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

示例1

输入: s = "leetcode", wordDict = ["leet", "code"]

输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet""code" 拼接成。

示例2

输入: s = "applepenapple", wordDict = ["apple", "pen"]

输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
     注意,你可以重复使用字典中的单词。

示例3

输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]

输出: false

思路

对于示例1,leetcode 能否由 ["leet", "code"] 拼接而成,可以拆分为:

  • l 是否为单词表的单词,以及剩下的 eetcode 能否被单词表拼接而成

  • le 是否为单词表的单词,以及剩下的 etcode 能否被单词表拼接而成

  • lee 是否为单词表的单词,以及剩下的 etcode 能否被单词表拼接而成

  • ...类推

如果上述有一条满足,则表示对应的字符串,可以有单词表拼接而成;

那么上述的解法,可以用 回溯算法 处理:

  • 1、定义函数 dfs(start),表示当前位置 start 开始的字符串,能否被单词表拼接而成

  • 2、从 start 开始,移动指针到 startEnd,如果这区间的字符串,在单词表中,则考虑 dfs(startEnd + 1)

  • 3、如果当前 start 到了字符串尾部,则返回为 true

DFS解法

    /**
     * @param {string} s
     * @param {string[]} wordDict
     * @return {boolean}
     */
    var wordBreak = function(s, wordDict) {
        const n = s.length;
        // 先将单词表转换为set结构,方便查询单词
        const set = new Set(wordDict);

        // 判断start开始的字符串,能够被单词表拼接
        const dfs = start => {
            // 到了字符串尾部,则认为可以 
            if(start == n) return true
            
            // 当前的部分字符串
            let tempS = '';
            for(let i = start; i < n; i++) {

                // 移动指针,将字符串累加
                tempS += s.charAt(i);

                // 如果单词表有该字符串,并且后面的也能被单词边拼接而成,则从 start 开始的都可以被单词表拼接
                if(set.has(tempS) && dfs(i + 1)) {
                    return true;
                }
            }

            // 上面没有返回,则 start 开始的,不能被单词表拼接
            return false;
        }

        // 从0开始
        return dfs(0);
    };

上述解法,在遇到case "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" ["a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"] 运行超时了

分析:

上面的算法,其实存在很多重复的计算,例如对于前半部分 a ,在单词表中,后续需要计算 start = 2, 3, 4...,最后由于尾部存在 b,无法拼接;而后就会进行 前半部分为 aa,在单词表中,后续需要计算 start = 3, 4, 5 ...

上述重复的计算是不必要的,可以将已计算的结果存下来,下次直接使用,通常这称为 DFS记忆优化

记忆优化DFS

    var wordBreak = function(s, wordDict) {
        const n = s.length;
        const set = new Set(wordDict);

        // 将已计算的结果存下来,index 为 key,memo[index] 为计算结果
        const memo = new Array(n);

        const dfs = start => {
            if(start == n) return true;

            // 如果已计算,则直接返回
            if(memo[start] !== undefined) return memo[start];

            let tempS = '';
            for(let i = start; i < n; i++) {
                tempS += s.charAt(i);

                if(set.has(tempS) && dfs(i + 1)) {
                    // 将结果存下来
                    memo[start] = true;
                    return true;
                }
            }
            // 将结果存下来
            memo[start] = false;
            return false;
        }

        return dfs(0);
    };

小结

DFS 算法往往在遇到极端情况时,会出现超时、重复计算的问题,而使用额外数据结构,将已计算的结果存下来,能够避免这类问题。

LeetCode 👉 HOT 100 👉 单词拆分 - 中等题

合集:LeetCode 👉 HOT 100,有空就会更新,大家多多支持,点个赞👍

如果大家有好的解法,或者发现本文理解不对的地方,欢迎留言评论 😄