【1.9】动态规划-解单词拆分

一、题目

        给定一个非空字符串s和一个包含非空单词的列表wordDict,判定s是否可以被空格拆分为一个或多个在字典中出现的单词。
 
说明:
1. 拆分时可以重复使用字典中的单词。
2. 你可以假设字典中没有重复的单词。

 

示例1:

81f332e3a4724a278270eaacc5d6e3c6.png

示例2:

ef22da441d394c68a1e5ed61b510aa99.png

示例3:

27070c3f32af4798a61107d1cdefd214.png

 

二、求解思路

        这道题目要求我们将字符串 `s` 分割成若干子串,并验证这些子串是否全部包含在给定的字典 `wordDict` 中。

        我们定义 `dp[i]` 表示字符串 `s` 的前 `i` 个字符是否可以被成功拆分,并且所有拆分出的子串都存在于字典 `wordDict` 中。为了求解 `dp[i]`,我们需要考虑从第 `i` 个字符向前截取 `k` 个字符,检查子串 `[i-k+1, i]` 是否在字典 `wordDict` 中,同时确保前面的子串 `[0, i-k]` 也能被正确拆分且其子串也都存在于字典中。

        具体来说,我们需要遍历字符串 `s` 的每个位置 `i`,并尝试从当前位置向前截取不同长度的子串,验证这些子串是否在字典中,并且之前的部分是否也能被正确拆分。通过这种方式,我们可以逐步构建出 `dp` 数组,最终判断整个字符串 `s` 是否可以被成功拆分。如下图所示

282dddd5fcae4804a2f70be9944c22dd.png

 

 

三、代码实现

        前面[0,i -k]子串拆分的子串是否都存在于 wordDict 中只需要判断 dp[i -k] 即可,而子串 [i -k+1,i]是否存在于字典 wordDict 中需要查找,所以动态规划的递推公式很容易列出来
 
        dp[i] = dp[i - k] && (wordDict.find(s.substr(i - k, k)) != wordDict.end());
 
这个k我们需要一个个枚举,我们来看下最终代码

 

#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>

bool wordBreak(const std::string& s, const std::vector<std::string>& dict) {
    std::unordered_set<std::string> wordDict(dict.begin(), dict.end());
    std::vector<bool> dp(s.length() + 1, false);
    dp[0] = true; // 空字符串总是可以被拆分的

    for (int i = 1; i <= s.length(); i++) {
        // 枚举k的值
        for (int k = 0; k <= i; k++) {
            // 如果往前截取全部字符串,我们直接判断子串[0,i-1]
            // 是否存在于字典wordDict中即可
            if (k == i) {
                if (wordDict.find(s.substr(0, i)) != wordDict.end()) {
                    dp[i] = true;
                    continue;
                }
            }
            // 递推公式
            dp[i] = dp[i - k] && (wordDict.find(s.substr(i - k, k)) != wordDict.end());
            // 如果dp[i]为true,说明前i个字符串结果拆解可以让他的所有子串
            // 都存在于字典wordDict中,直接终止内层循环,不用再计算dp[i]了。
            if (dp[i]) {
                break;
            }
        }
    }
    return dp[s.length()];
}

int main() {
    std::string s = "leetcode";
    std::vector<std::string> dict = {"leet", "code"};
    bool result = wordBreak(s, dict);
    std::cout << std::boolalpha << result << std::endl; // 输出: true
    return 0;
}
        上面代码有一个判断,就是截取的是前面全部字符串的时候要单独判断,其实当截取全部
的 时 候 我 们 只 需 要 判 断 这 个 字 符 串 是 否 存 在 于 字 典 wordDict 中 即 可 , 可 以 让 dp[0] 为 true,dp[0]表示的是空字符串。这样代码会简洁很多,我们来看下

 

#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>

bool wordBreak(const std::string& s, const std::vector<std::string>& dict) {
    std::unordered_set<std::string> wordDict(dict.begin(), dict.end());
    std::vector<bool> dp(s.length() + 1, false);
    dp[0] = true; // 边界条件

    for (int i = 1; i <= s.length(); i++) {
        for (int j = 0; j < i; j++) {
            dp[i] = dp[j] && (wordDict.find(s.substr(j, i - j)) != wordDict.end());
            if (dp[i]) {
                break;
            }
        }
    }
    return dp[s.length()];
}

int main() {
    std::string s = "leetcode";
    std::vector<std::string> dict = {"leet", "code"};
    bool result = wordBreak(s, dict);
    std::cout << std::boolalpha << result << std::endl; // 输出: true
    return 0;
}
        这个和第一种写法不太一样,这个每次截取的方式如下图所示。

 

b34344e887c840fdad3d6c852d26fa4e.png

 

        这道题目可以被视为一个完全背包问题,其中背包是字符串 `s`,而可供选择的商品则是字典中的字符串。由于字典中的字符串可以被无限次选择且没有数量限制,因此这个问题符合完全背包问题的特征。完全背包问题将在后续的讲解中详细探讨。

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值