给定一个非空字符串 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”。
注意你可以重复使用字典中的单词。
这个题目最朴素的做法就是递归。
假设字符串为s=“leetcode”,欲看s能否被拆分即看s是否能被分成两个部分,第一部分是一个子字符串且该子串在字典里,第二部分看剩余的子串能否被拆分。至于第二部分的判断即可采用递归解法了。这样解的复杂度很高,会超时。
对于第二部分子串的判断其实包含了很多重复的操作并且很多子问题都是相同的,那就自然而然想到用DP来解此题。
以示例2为例,DP的思路与递归是一样的,只不过一个自上而下(存在解很多重复子问题的过程,一个是自下而上,通过数组来保存子问题的解)。为了判断S能否被拆分,即看S能否被拆分成两部分S1,S2,其中S1也能被拆分。那我们可以用一个数组dp保存子串能否被拆分,dp[len]表示长度为len的子串能否为拆分,设置dp[0]=true,即空串默认可以的,这也是初始状态。欲看长度为len的子串能否被拆分,即看该子串能否被分成两部分,前一部分的子串S1已经解出来是可分的,后一部分子串正好也在字典里,那长度为len的字符串就是可拆分的。循环求解出不同子串的解,最后即可得欲求的字符串是否有解。
/*
递归解法
*/
bool wordBreak(string nums, int begin, unordered_set<string> s)
{
if (begin >= nums.size()) return true;
int end = begin + 1;
while (end<=nums.size())
{
if (s.count(nums.substr(begin, end - begin)) && wordBreak(nums, end, s)) return true;
end++;
}
return false;
}
/*
DP解法
*/
bool wordBreak(string nums,unordered_set<string> s)
{
vector<bool> dp(nums.size() + 1, false);
dp[0] = true;
for (int len = 1; len <= nums.size(); len++)
{
for (int i = 0; i < len; i++)
{
string tmp = nums.substr(i, len - i);
if (dp[i] && s.count(tmp))
{
dp[len] = true;
break;
}
}
}
return dp[nums.size()];
}
int main()
{
string nums= "leetcode";
unordered_set<string> s = {"leets","code"};
cout << wordBreak(nums,s);
system("pause");
}