题目 单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-break
思路
等待了一段时间的动态规划题目。既然是适合使用动态规划解决的题目,我个人是挺期待动归的题目来进行必要的练习的。不过还是要叹口气,在找到官解下面的优秀解法前,我还真没想到这道题的动态规划具体思路。
本质上来说,这道题使用动态规划的时候,要秉承此前动态规划常用的两大思路,一个是最优子问题思路,另一个则是额外的空间占用。毕竟动态规划往往都要使用额外空间来换取时间,那么这个额外的空间怎样使用呢?结合最优子问题,可以得到这样的答案:
采用一个长度和单词长度一样的数组,数组的第x位表示第x位之前的字母能否使用单词表里面的单词进行表示。那么不难得到后面的第y位能否被表示呢,就采用顺序查一下此前能被表示的第x位,第x-3位……第0位再加上y - x,y - x + 3, y位单词能不能被表示。具体原理可以采用示例3进行演示:
c | a | t | s | a | n | d | o | g | |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
不难得到因为末尾单词g往前查,d开始的o和g无法找到单词,前面的s和t开始也找不到单词去符合要求,所以返回false。顺便一提,单词的开头之前应当是1。
那么尝试代码实现一下这个动态规划的思路:
代码实现
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_map<string, int> data;
int max_length = 0;
vector<int> dp_map;
for (int i = 0; i < wordDict.size(); i++) {
data.insert(pair<string, int> (wordDict[i], i));
if (wordDict[i].size() > max_length) {
max_length = wordDict[i].size();
}
}
dp_map.push_back(1);
for (int i = 0; i < s.size(); i++) {
int first = max(i - max_length, 0);
bool flag = false;
for(int j = first; j < i + 1; j++) {
if (dp_map[j] == 1) {
string new_word = s.substr(j, i - j + 1);
if (data.find(new_word) != data.end()) {
dp_map.push_back(1);
flag = true;
break;
}
}
}
if (!flag) {
dp_map.push_back(0);
}
}
return dp_map.back();
}
};
自己写代码的时候居然花了不少时间,其中比较重要的就是在内外循环的i和j的取值范围花了不少时间去调整。以及加入了单词长度判定,超过单词表最长单词长度的部分舍弃掉,可以节省一定的时间。
意外的是空间结果竟然是100%,按理来说动归不应该算比较省空间的啊,哈哈。