leetcode 139. 单词拆分I,II

题目I

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

分析

  • 本题和322题的零钱兑换很类似,即字典中的单词看做是硬币,将字符串s看作是要凑的硬币总数,可以选择字典中的0个或者是多个硬币
  • 定义状态dp[i]表示从左到右长度为i的字符串是否可以被正确拆分
  • dp[i] = anyof(dp[j] && s[j+1, i])
  • 为加快在wordDict的检索速度,则将wordDict中的元素都放入unordered_set中

代码

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict){
        //本题和322题的零钱兑换很类似,即字典中的单词看做是硬币,将字符串s看作是要凑的硬币总数,可以选择字典中的0个或者是多个硬币
        //定义状态dp[i]表示以s[i]结尾的字符串是否可以被正确拆分
        //dp[i] = anyof(dp[j] && s[j+1, i])
        //为加快在wordDict的检索速度,则将wordDict中的元素都放入unordered_set中
        unordered_set<string> dic(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size()+1, false);
        dp[0] = true;
        for(int i=1;i<=s.size();i++){//1表示字符串的长度为1, 可以把这个长度当做是兑换硬币问题的要被兑换的币值
            for(int j=i-1;j>=0;j--){//j表示回退指针
                if(dp[j] && dic.find(s.substr(j, i-j))!=dic.end()){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }
};

题目II

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入:
s = “catsanddog”
wordDict = [“cat”, “cats”, “and”, “sand”, “dog”]
输出:
[
“cats and dog”,
“cat sand dog”
]

分析

本题相对于上一题就是要求出解空间,并求出所有的解。 主要的方法就是深度搜索+DP动态规划。对于求解空间即满足条件的所有的解一般就是使用深度搜索。所有任意长度的前缀是否可拆分是知道的(可以通过在上述动态规划问题中进行存储下来),那么如果后缀子串在单词集合中,这个后缀子串就是解的一部分。因此可以画出递归树如下:
在这里插入图片描述

注意从叶子结点到根节点的一条路径就是一个解,但是对于递归过程来说,如果我们以path来存储从根结点到叶子结点的路径上的单词,那么最终将路径上的单词拼接成字符串的时候,就要进行翻转一下,或者是在path进行收集的时候就每次在path头部进行插入。

class Solution {
public:
    vector<string> wordBreak(string s, vector<string>& wordDict) {
        //定义状态dp[i]表示以s[i]结尾的字符串是否可以被正确拆分
        //dp[i] = anyof(dp[j] && s[j+1, i])
        //为加快在wordDict的检索速度,则将wordDict中的元素都放入unordered_set中
        vector<string> res;
        unordered_set<string> dic(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size()+1, false);
        dp[0] = true;
        vector<vector<bool> > flag(s.size()+1, vector<bool>(s.size(), false));
        for(int i=1;i<=s.size();i++){//1表示字符串的长度为1, 可以把这个长度当做是兑换硬币问题的要被兑换的币值
            for(int j=i-1;j>=0;j--){//j表示回退指针
                if(dp[j] && dic.find(s.substr(j, i-j))!=dic.end()){
                    dp[i] = true;
                    flag[i][j] = true;// flag[i][j]表示的是s[j, i)是否可以被正确分割
                }
            }
        }
        vector<string> path;
        dfs(s, wordDict, flag, path, res, s.size());
        return res;
    }

    void dfs(string s, vector<string>& wordDict, vector<vector<bool> >& flag, vector<string>& path, vector<string>& res, int end) {
        if(end==0){
            string ans = "";
            for(auto it = path.crbegin();it!=path.crend();it++){
                ans += *it + " ";
            }
            ans.erase(ans.end()-1);
            res.push_back(ans);
        }
        for(int i=end-1;i>=0;i--){
            if(flag[end][i]){
                path.push_back(s.substr(i, end-i));
                dfs(s, wordDict,flag, path, res, i);
                path.pop_back();
            }  
        }
    }
};

也可以正着进行求解路径,但是却超时了。主要的思想就是类似分割回文串

class Solution {
public:
    vector<string> wordBreak(string s, vector<string>& wordDict) {
        //定义状态dp[i]表示以s[i]结尾的字符串是否可以被正确拆分
        //dp[i] = anyof(dp[j] && s[j+1, i])
        //为加快在wordDict的检索速度,则将wordDict中的元素都放入unordered_set中
        vector<string> res;
        unordered_set<string> dic(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size()+1, false);
        dp[0] = true;
        vector<vector<bool> > flag(s.size()+1, vector<bool>(s.size(), false));
        for(int i=1;i<=s.size();i++){//1表示字符串的长度为1, 可以把这个长度当做是兑换硬币问题的要被兑换的币值
            for(int j=i-1;j>=0;j--){//j表示回退指针
                if(dp[j] && dic.find(s.substr(j, i-j))!=dic.end()){
                    dp[i] = true;
                    flag[i][j] = true;// flag[i][j]表示的是s[j, i)是否可以被正确分割
                }
            }
        }
        vector<string> path;
        dfs(s, wordDict, flag, path, res, 0);
        return res;
    }

    void dfs(string s, vector<string>& wordDict, vector<vector<bool> >& flag, vector<string>& path, vector<string>& res, int start) {
        if(start>=s.size()){
            string ans = "";
            for(auto it = path.begin();it!=path.end();it++){
                ans += *it + " ";
            }
            ans.erase(ans.end()-1);
            res.push_back(ans);
        }
        for(int i=start;i<s.size();i++){
            if(flag[i+1][start]){
                path.push_back(s.substr(start, i-start+1));//之前是end固定,现在是start固定
                dfs(s, wordDict,flag, path, res, i+1);
                path.pop_back();
            }  
        }
    }
};


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值