简单的动态规划问题,采用自顶向下的备忘录方法,代码如下:
class Solution {
public:
bool dictContain(unordered_set<string> &dict, string s)
{
unordered_set<string>::iterator ite = dict.find(s);
if(ite != dict.end())
return true;
else return false;
}
bool wordBreak(string s, unordered_set<string> &dict)
{
// Note: The Solution object is instantiated only once and is reused by each test case.
if(dict.empty())
return false;
const int len = s.size();
bool canBreak[len]; //canBreak[i] = true 表示s[0~i]是否能break
memset(canBreak, 0, sizeof(bool)*len);
for(int i = 1; i <= len; i++)
{
if(canBreak[i-1] == false && dictContain(dict, s.substr(0, i)))
canBreak[i-1] = true;
if(canBreak[i-1] == true)
{
if(i == len)return true;
for(int j = 1; j <= len - i; j++)
{
if(canBreak[j+i-1] == false && dictContain(dict,s.substr(i, j)))
canBreak[j+i-1] = true;
if(j == len - i && canBreak[j+i-1] == true)return true;
}
}
}
return false;
}
};
注意:在本机调试时,编译器要开启c++11支持,因为#include<unordered_set>是c++11的标准
9.18更新-------------
第一开始我做此题的时候,用了二维的DP,结果总是TLE。后来看了答案发现只用一维DP就可以了。
那为什么这里只用一维DP就行了呢,因为...... 因为.... 因为起点和终点都是确定的!!!!正因为起点和终点都是确定的,所以我们没有必要进行二维DP,平白无故地浪费了大量的时间复杂度!!!
另外,需要注意,上面的解法和下面的解法是不同的!!!!,上面的解法是先找到一个从0开始的到i可以break的子串,然后再从i+1开始向后搜索,看能不能形成更大的可以break的子串。而第二种解法是对一个从0到i的子串,通过判断它的前半部分是否可以break并且后半部分是否在wordDict,来决定这个从0到i的子串是否可以break。通过比较发现,前面的那个方法,侧重于向串的后面进行搜索,后面的方法侧重于向串的前面进行搜索。(这里的前,指的是下标越来越大。)
思路:Word Break I
看到这题第一反应是DFS枚举查找,直到“探”到string尾部则算成功。但题目并不要求给出是如何break的,而只要判断是否能break。对这类判断“是”与“否”的可以用DFS暴力解决的题,可以尝试用DP做book keeping中间判断状态,避免DFS的反复搜索。比如这题,可以用一个数组dp记录S[0:i]是否能break。
dp[i]:S[0:i-1]是否能被break。例如dp[1]表示s[0]是否能被break。
dp[0] = true
dp[i] = true if and only if:
1. 存在一个i-1>=k>=0,使s[k:i-1]存在于dict中。
2. s[0: k-1]可以被break,即dp[k] = true。
class Solution {
public:
bool wordBreak(string s, unordered_set<string> &dict) {
vector<bool> dp(s.size()+1,false);
dp[0] = true;
for(int i=0; i<s.size(); i++) {
for(int j=i; j>=0; j--) {
if(dp[j] && dict.count(s.substr(j,i-j+1))!=0) {
dp[i+1] = true;
break;
}
}
}
return dp[s.size()];
}
};