Skip to content

Latest commit

 

History

History
101 lines (88 loc) · 3.88 KB

139. Word Break.md

File metadata and controls

101 lines (88 loc) · 3.88 KB

139. 单词拆分

给定一个非空字符串 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

解法一:

//时间复杂度O(n^2), 空间复杂度O(n)
class Solution {
public:
    bool wordBreak(const string& s, const unordered_set<string>& us,
                   vector<int>& vec, int start) {
        if(start == s.size()) return true;
        if(vec[start] != -1) return vec[start];
        for(int i = start + 1; i <= s.size(); i++) {
            if(us.count(s.substr(start, i - start)) &&
               wordBreak(s, us, vec, i)) {
                vec[start] = 1;
                return true;
            }
        }
        vec[start] = 0;
        return false;
    }
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> us;
        for(string& word : wordDict) us.insert(word);
        vector<int> vec(s.size(), -1);
        return wordBreak(s, us, vec, 0);
    }
};
//时间复杂度O(n^2), 空间复杂度O(n)
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> us;
        for(const string& word : wordDict) us.insert(word);
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for(int i = 1; i <= s.size(); i++) {
            for(int j = 0; j < i; j++) {
                if(dp[j] && us.count(s.substr(j, i - j))) {
                    dp[i] = true;
                }
            }
        }
        return dp[s.size()];
    }
};

解法一:

记忆优化递归。先考虑暴力搜索法,需要遍历每一个前缀,只有前缀包含在wordDict中才继续向下递归。但是暴力法效率太低,不能通过测试。

所以就将其n个后缀的结果存储在数组vec中,vec[i]==1说明s.substr(i)满足拆分要求,vec[i]==0说明不满足,vec[i]==-1说明未确定,此时需要递归向下进行。

递归树最深为n,所以空间复杂度为O(n);时间复杂度暂时没想明白,求指导。

解法二:

动态规划。突破点同解法一,只不过解法二是从左向右走。假设设字符串s的某一前缀s.substr(0,j)(包括s[0]但不含s[j])满足拆分要求,如果s的子串s.substr(j, i-j)出现在wordDict中,那么该前缀与该子串的组合s.substr(0,i)也满足拆分要求。所以可用长为n+1的数组dp来记录状态,其中dp[i]为真代表从0到i的前缀满足拆分要求。所以有如下递推关系:

if(dp[j] && us.count(s.substr(j, i - j))) dp[i] = true;

可以知道空串也算出现在wordDict中,即dp[0]为真。

通过从前向后递推,遍历所有i和j的位置,最终dp[s.size()]就是所求的答案。其余细节如上代码。

2019/12/18 19:18