题目一
力扣:139. 单词拆分
思路:动态规划
1、首先,要看数据约定,s的长度最大300,而字典长度1000,所以我们在思考算法的时候,一定也要考虑数据量的问题,在切入问题的时候,一般要从数据量小的地方切入(也不一定,做题不要太死,得灵活),详细缘由见我下方分解。
2、一定要认真读题,题中说的是只能通过在字母之间添加空格来分割,不能是从中间判断出来一个词在字典中,然后删除,再把两边合并的判断方式,所以无序的判断很麻烦会很复杂,所以我们直接就模拟平常我们分割单词的方法,直接从前到后的找地方加空格就行。
确定DP算法
我们拿到一个串,看他是不是满足题意,要么它本身就在字典里,要么,它可以从前到后分成若干端,每端都在字典里,这样也符合题意,所以这个就是典型的DP,大状态由若干个小状态决定,存在转移方程,是一个多阶段决策问题。
状态
我们通过之前的分析得出,没必要任取区间,这只会添加麻烦,我们只需要从前到后寻找并添加就行,也就是所说的起点固定,所以只需要一个状态 “i” 标识出我们找到哪里了。
初始值
全是false即可
转移方程
1、先判断它本身在不在字典里,在的话直接true
2、反之,从0开始一直到i,枚举断点,如果分开后的两段都是可拆分的,那么这个大区间就是可拆分的。
找答案
dp[n-1]即,最后一个元素处的true or false即为答案。
代码
class Solution {
public:
bool dp[305] = {
false };
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> item;
for (string tmp : wordDict) {
item.insert(tmp);//将字典存在HashMap里,方便快速查找
}
int n = s.size();
for (int i = 0; i < n; i++) {
string strs = s.substr(0, i + 1);//看看这个段本身在不在字典
if (item.count(strs) > 0) {
//在字典里
dp[i] = true;
}
else {
//不在字典里
for (int j = 0; j < i; j++) {
//枚举断点
string str = s.substr(j + 1, i - j);
if (dp[j] && (item.count(str) > 0) == true) {
//分出的两段都可拆分
dp[i] = true;
break;
}
}
}
}
return dp[n - 1];
}
};
所有代码均以通过力扣测试
(经过多次测试最短时间为):
错误思路:KMP+BFS(这里只给出KMP类的代码,作为KMP的复习)
1、我最开始的思路就是先写了一个KMP算法用于匹配,然后根据字典进行一个BFS,每次遍历字典去匹配字符串,匹配上了就把匹配部分更改一下,做个标识,如果最后能完全被替换就返回true。(其实这里就看出这个算法一点都对,因为你去匹配到了,需要做很多很多额外处理,去记录,远远没有从头到尾去找分割点的办法符合题意)
2、经过实践证明,这个思路有问题,会超时。让我们回想一下上文,字典有足足1000个单词,每次BFS会造成大量无用状态产生,所以会超时的
3、所以综上,在选择策略的时候,我们不能完全的发散的思考
<1> 第一就是一定要读好题,把题意充分的正确的了解清楚,通过读懂题意才是我们正确的找到解题办法的最重要途径
<2> 第二就是看数据约定,这是我们检验算法合理性最重要的途径,就比如我们这个算法,在数据量1000的时候,根本没有可行性,所以在这里的时候就可以PASS掉,这也侧面说明了,我们思考算法也要考虑数据量,根据数据量找到最合适最快速最简单的解决办法。
以下只写出KMP部分代码以作复习:
class KMP {
public:
int kmp(string source, string item) {
vector<int> next = find_next(item);
int n = source.size();
int n_item = item.size();
int k = -1;
for (int i = 0; i < n; i++) {
while (k > -1 && source[i] != item[k + 1]) {
k = next[k];
}
if (source[i] == item[k + 1]) {
k++;
}
if (k == n_item - 1) {
return i;
}
}
return -1;
}
private:
vector<int> find_next(string item) {
//求next数组相当于自己匹配自己
int n = item.size();
vector<int> next(n, -1);
int k = -1;//k的位置就是+1后前面都是匹配好了的,+1之后就是当前最长的公共前缀个数
//并且恰好也是此次比较开始位置,如果也一致,公共前缀就++,反之就回溯到上一次比较的地方,直到-1为止
//不懂的话 aabbaa+最长公共前缀,自己模拟一遍就想的通了
for (int i = 1; i < n; i++) {
while (k > -1 && item[i] != item[k + 1]) {
//k初始值是-1,所以不管啥操作都得加一
k = next[k];
}
if (item[i] == item[k + 1]) {
k++;
}
next[i] = k;
}
return next;
}
};