题目描述
给一个字符串,和一些单词,问字符串能不能由这些单词构成。每个单词可以用多次,也可以不用。
示例:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
解题思路
-
刚开始是超级暴力的想法,利用回溯法(找全排列),用
wordDict
去生成所有可能的字符串。期间如果出现了目标字符串s
,就返回true
。(这种方法基本毫无疑问会超时) -
动态规划:这里最重要的是要想明白如何确定“状态表示”(即状态应该定义为“什么的状态”,参考53、300题),然后如何状态转移(如何由已知子问题的最优解得到大问题的最优解)。
其实下面那个练习和第一种dp思路一致,不需要看。
注:动态规划和记忆化搜索本质没区别,《算法导论》中统称为“动态规划”。(只是方向不一样,在评论区)
动态规划是打表格法,自底向上解决问题,其特点是:每一次遇到大问题的时候,与这个大问题有关的所有小问题都得到了解决。
记忆化搜索是自顶向下解决问题,每一次遇到大问题的时候,与这个大问题有关的小问题未必都得到了解决,但是解决了一个我们就记录一下,以防止以后再遇到同样问题的时候从头开始再计算一次。
我想“自底向上”与“自顶向下”是它们最主要的区别:
1、“自顶向下”是人的视角,我们面对问题的时候,是直接解决问题,遇到一个问题解决一个问题,所以需要记忆,就像我们工作中遇到问题要写个笔记或者博客记录下来一样。
2、“自底向上”有些资料上说是开启了上帝视角,我很认同这种说法。显然动态规划也是用空间换时间,也得记录,从最小的问题开始做,直到解决了最终的大问题。用动态规划我个人觉得比较好的一点是,解决问题的方向比较有规律,因此状态压缩就成为了可能,不像记忆化搜索那样,解决的问题到处跑。
有的资料,例如《算法导论》对它们二者就不区分,都叫动态规划。
参考代码
class Solution {
public:
bool wordBreak(string str, vector<string>& wordDict) {
int length = str.length();
if(length == 0)
return false;
unordered_set<string> uset(wordDict.begin(), wordDict.end());
bool dp[length];
memset(dp, 0, sizeof(dp));
if(uset.count(str.substr(0, 1)) > 0)
dp[0] = true;
for(int i = 1; i < length; i++){
if(uset.count(str.substr(0, i+1)) > 0){
dp[i] = true;
continue;
}
for(int j = 0; j < i; j++){
if(dp[j] && uset.count(str.substr(j+1, i-j)) > 0){
dp[i] = true;
break;
}
}
}
return dp[length - 1];
}
};