问题描述:
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
Note:
· The same word in the dictionary may be reused multiple times in the segmentation.
· You may assume the dictionary does not contain duplicate words.
Example 1:
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".
Example 2:
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
Note that you are allowed to reuse a dictionary word.
Example 3:
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false
思路解析:
题目中给了两个已知量,一个是字符串s,另一个是含有字符串的数组——字典
判断字符串s 是否 可以由字典中的子串 组成
**************************************************************************** 初步分析
第一眼过后,思路混乱!!!因为字符串s可以由多个子串组成,也可以由单个或几个子串重复组成。即不断地遍历字符串s,并看字符串s的子区间是否存在于字典中;已遍历过的区间若存在于字典中,则记录,并遍历判断字符串s剩下的区间;若剩下的区间也存在于字典中,则正确。第一反应解决办法:
1. 遍历字符串s,
2. 找到第一个存在于字典中的子串区间
3. 取出剩下的子串区间,并重复第1步
4. 索引到end后,最后一个子串也存在于字典,则返回true
****************************************************************************** 动态规划
以上思路感觉不容易复现成代码,看了看别人的博客,用的是动态规划思想。!!! 这道题竟然也用动态规划思想,动态规划即当前状态可以由之前已计算出的状态得到。放在这个问题中就是当前位置处的子串是否可以由字典组成,取决于之前判断过的子串。
DP思想的难点在于:定义dp数组,找出状态转移方程
1. 定义dp数组
这里用一个一维的数组 dp[i] 表示范围字符串s中 [0, i) 子串是否可以拆分(即是否由字典中单词组成)
dp数组的长度应为字符串 s.size()+1,因为dp[0]表示空字符情况,且为true
2. 状态转移方程
用两层for循环,第一层是i,第二次是j。我们用 j 把 [0, i)范围内的子串 分为了两部分:[0, j) 子串和 [j, i) 子串。
其中范围 [0, j) 就是dp[j],范围 [j, i) 就是s.substr(j, i-j)。其中 dp[j] 是之前已经判断过的状态,我们已经算出来了只需要在 字典中查找s.substr(j, i-j)是否存在了,如果二者均为true,将dp[i]赋为true。此时,即字符串s,[0, i)子串可以拆分,那么接着遍历,重复上面过程,直到遍历完整个字符串s。
动态规划应用于此问题中,即需要得到字符串s此时的状态 dp[s.size] ———— [0, s.size)子串,即字符串s是否可以拆分,而dp[s.size]状态为动态规划问题中最后一个状态,应由之前的状态得到。而之前的某一状态dp[i],也是由再之前的状态dp[j]和判断s.substr(j, i-j)是否存在于字典后得到。如此一来,便清楚了。
代码实现:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// 动态规划思想
// 1、建立动态数组 2、找出状态转移方程
vector<bool> dp (s.size()+1, false);
dp[0] = true; // 表示包含空字符子串
for (int i = 0; i < dp.size(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && find(wordDict.begin(), wordDict.end(), s.substr(j, i-j)) != wordDict.end()) {
dp[i] = 1;
break;
}
}
}
return dp.back();
}
};
学习内容:
1. 再一次练习了动态规划,注意找出dp数组和状态转移方程。如何判断动态规划还是有待且很有待的提高。
2. 代码语言上,用了STL库里的find函数,用于查询数组
3. C++ substr用法:s.substr(pos, n),返回一个string,包含s中从pos开始的n个字符的拷贝(pos的默认值是0,n的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
4. vector中的back(),表示容器的末尾的引用,即末尾的数。同样front()表示前端的引用。有别于begin()和end()是返回的迭代器,类似于指针。