力扣139. 单词拆分

题目一

力扣: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;
	}
};

Guess you like

Origin blog.csdn.net/qq_45678698/article/details/121342790