[C++]LeetCode: 112 Word Break(DP)

本文探讨了字符串分割算法,包括递归、动态规划及BFS三种方法。递归方法简单直观但效率较低;动态规划方法通过自底向上构建状态,显著提高了效率;BFS则提供了一种全新的遍历视角。

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

题目:

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given
s = "leetcode",
dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".

Answer 1: 递归,自顶向下(TLE)

思路:从s的第一个字母开始匹配,逐个递增子串长度,如果前缀可以匹配,再匹配后面的子串。

但是这种方法,时间复杂度为O(N^2),超过LeetCode时间限制。

Error Code:

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
        if (s.size() == 0) return false;
        return wordBreak_helper(s, dict, 0);
    }
    
private:
    bool wordBreak_helper(string s, unordered_set<string>& dict, int pos)
    {
        //pos表示截取字符串的起始位置
        if (pos == s.size())
            return true;
        
        for (int i = 1; i <= s.size()-pos; i++)
        {
            //截取子串,并判断是否在字典内
            string substr = s.substr(pos, i);
            if (dict.find(substr) != dict.end())
            {
                wordBreak_helper(s, dict, pos+i);
            }
        }
        
        return false;
    }
};

Answer 2: DP, 自底向上的动态规划

思路:我们从字符串末尾开始匹配。dp[i]表示子串S[i...len-1]是否可以根据字典进行拆分。dp[len]表示空串,dp[0]即s[0..len-1]为我们要求的答案。

每一次循环迭代时,我们进行一种新的拆分方式,用索引j 来拆分子串s[i..len-1],如果我们可以找到一个子串s[i..j]是在字典内,并且s[j+1..len-1](即dp[j+1])是可拆分的,则我们可以知dp[i]是可拆分的。而且s[i..j]总之在判断完s[j+1..len-1]后计算,即总之会先得到dp[j+1]的结果,我们可以通过DP算法存储之前的拆分结果。节省了判断时间。

具体可以看下图加以理解:

Attention:

1. 初始化动态规划数组时,我们要构建len+1个,因为从字符串末尾最后一个字母计算时,要知道dp[len], dp[len]表示空串,为true.

<span style="font-size:14px;">//dp[i]表示S[i..len-1]是否可break
        vector<bool> dp(len + 1, false);
        dp[len] = true;</span>
2. 我们在进行单词是否可拆分时,从后往前拆分,可以省去很多判断。考虑DP问题时,每次可以从自顶向下和自底向上,两个角度考虑,寻找最佳解决方案。

复杂度:O(N^2)

AC Code:

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) {
        int len = s.size();
        if(s.size() == 0) return false;
        
        //dp[i]表示S[i..len-1]是否可break
        vector<bool> dp(len + 1, false);
        dp[len] = true;
        
        for(int i = len - 1; i >= 0; i--)
        {
            for(int j = i; j < len; j++)
            {
                string substr = s.substr(i, j - i + 1);
                if(dict.find(substr) != dict.end() && dp[j+1] == true)
                {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[0];
    }
};


Answer 3: BFS

有种新的解法,利用BFS的方法来遍历字符串。可以看下leetcode这篇文章:A-solution-using-bfs

LeetCode 题目 1128 叫做 "构建最大数" (Build Maximum Number),这是一个关于字符串操作的动态规划问题。题目要求从给定的一系列无序整数数组中选择一些数,并按照非递减顺序连接成一个字符串。目标是找到可以构成的最大整数。 在 C++ 中,你可以通过以下步骤解决这个问题: 1. **创建结构体或类**:为了存储每个数字及其是否被选中,你可以创建一个结构体 `Choice` 包含整数值 `val` 和一个布尔值 `used` 表示是否被选中。 ```cpp struct Choice { int val; bool used; }; ``` 2. **动态规划表**:初始化一个二维动态规划数组 `dp`,其中 dp[i][j] 存储前 i 个数字组成长度为 j 的最大整数。 3. **遍历数组**:对于数组中的每个数字,检查是否能将其添加到当前状态(即 dp[i][j]),并更新结果。有两种情况:如果它大于等于下一个数字,则将当前数字加到上一状态;否则,直接复制上一状态。 4. **回溯**:当结束所有数字的选择时,遍历 dp 数组,找出最大的那个元素作为结果。 5. **处理边界条件**:记得处理空数组的情况以及数组中只有一个数字的情况,此时最大整数就是原数组本身。 ```cpp class Solution { public: string largestNumber(vector<int>& nums) { vector<Choice> choices(nums); sort(choices.begin(), choices.end(), [](const Choice& a, const Choice& b) { return to_string(a.val) < to_string(b.val); }); // 初始化 dp string res = ""; for (int n : nums) res += to_string(n); if (res[0] == '-') { choices.erase(remove_if(choices.begin(), choices.end(), [n](const Choice& c) { return c.val == -1; }), choices.end()); reverse(res.begin(), res.end()); } for (size_t i = 0; i < choices.size(); ++i) { for (size_t len = 1; len <= res.length(); ++len) { if (len > i) { string str = to_string(choices[i].val); bool valid = true; for (size_t j = len - 1; j >= i && valid; --j) { if (str[0] + res[j] < res[j]) { valid = false; break; } } if (valid) { res.insert(res.begin() + len, str[0]); choices[i].used = true; break; } } } } return removeDuplicates(res); } private: string removeDuplicates(string s) { string result; for (char ch : s) { if (result.find(ch) == string::npos) { result.push_back(ch); } } return result; } }; ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值