LeetCode单词拆分——动态规划
前言
字符串的问题很多都是动态规划解决的,LeetCode #139 单词拆分、#140 单词拆分II 是典型运用动态规划的两道题。
LeetCode #139 单词拆分
题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
算法思路
s 是有限字母组成的字符串,是否全部可分取决于两个因素:
- 子串是否在字典中
- 可分子串的前一个子串是否可分
第二个因素决定了这道题可以划分为一系列小问题,这是典型的动态规划问题。
代码实现
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string> st(wordDict.begin(), wordDict.end());
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] && st.find(s.substr(j, i - j)) != st.end()) {
dp[i] = true;
break;
}
}
}
return dp[s.size()];
}
};
LeetCode #140 单词划分II
题目描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
算法思路
#140 是 #139 的延续,这里不仅仅要求字符串可以完全划分,也需要重新组合成新的字符串。我们依据上一题的思路,重新规划动态规划数组。 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 j − i j-i j−i 之间的子串在字典中,且 i i i 之前的子串可以完全划分。
之后,在构造句子上,我们采用 unordered_map 数据结构。key 为字符串下标,value 为当前位置前可能组成的句子。
代码实现
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
int len = s.size();
set<string> st;
int minLength = INT_MAX, maxLength = 0;
for (const string& word : wordDict) {
if (word.size() < minLength) minLength = word.size();
if (word.size() > maxLength) maxLength = word.size();
st.insert(word);
}
vector<vector<int>> dp(len + 1);
dp[0] = {0};
for (int j = 1; j <= len; ++j) {
for (int i = max(0, j - maxLength); i <= j - minLength; ++i) {
if (!dp[i].empty() && st.count(s.substr(i, j - i)) {
dp[j].push_back(i);
}
}
}
if (dp[len].empty()) return vector<string>();
unordered_map<int, vector<string>> m;
for (int j = 1; j <= len; ++j) {
if (dp[j].empty()) continue;
for (const int& i : dp[j]) {
if (m.find(i) == m.end()) {
m[j].push_back(s.substr(i, j - i));
} else {
for (const string& ss : m[i]) {
m[j].push_back(ss + ' ' + s.substr(i, j - i));
}
}
}
}
return m[len];
}
};
@北京·海淀 2019.12.17