*************
C++
*************
简单看一眼题目:
这个就是判断是否是子集的问题。突然想到这个题目在实际生活中有哪些应用呢,不然做题没有啥积极性。第一个想到的就是我高中考试的时候,那个英语卷子有整整16页,然后我脑子里的wordDict并没有那么多的词汇,导致我输出的时候经常输出不对。就好像一个女孩子说 'Hey, you are the apple of my eye.' 然后呢,我的wordDict不允许我浪漫,就会输出‘嚯,这女的说你是她眼里的异物,这个异物有苹果那么大,搁这阴阳怪气你呢。’
好的,这个起手肯定就是,到这里我不是很确定了,之前的起手都是
int n = array.size();
但是这里没有array,拔剑四顾心茫然。但是除了矩阵,我还喜欢穷举,那就直接举例子吧,如果我是世界上最NB的芯片,我看一眼就知道 s 在不在 m 里,虽然不知道大脑怎么处理的,但是就是一眼就能看出来谁是sm。好的,对于计算机来说,可能优势就是非常的严谨,计算非常的快速,那就直接暴力的穷举,也是很优雅的,就好像Dr. Strang一样。
奇异博士没档期,请卷福来客串一下。
在‘最长回文子串’中学到的一个小技巧在这里用到了,s.substr(0, 1)表示在s中,从第一个字母开始取字母,只取第一个字母。这里举一反三一下,某天张三去了一个商K,妈妈桑领进来一串字母,你在心里从左到右给他们标上了0-13的编号,这里共14个字母,对妈妈桑说‘s.substr(1,2)’,妈妈桑就懂了,从左往右数,第一个字母开始,安排两个到你的 char 里面,为啥要char 而不是 int 呢?因为char相当于大床房,有1个字节,最多可容纳8位。但是 int 有2个字节,相当于标间了。
这里又学到一个新的语法, unordered_set, 这个代表启用快速查找,有很多多的优点:
-
查找效率:
unordered_set
在 C++ 中是基于哈希表实现的,它提供了平均时间复杂度为 O(1) 的查找性能。这意味着检查一个单词是否存在于wordDict
中是非常快速的。 -
避免重复:
unordered_set
只存储唯一的元素,如果wordDict
中有重复的单词,使用unordered_set
可以自动去除重复项,虽然在这个特定问题中,重复单词是被允许的,但去除重复项可以减少哈希表的大小,从而可能提高性能。 -
代码简洁性:使用
unordered_set
可以简化代码,因为不需要额外的数据结构来存储单词,也不需要手动检查单词是否已经存在于集合中。 -
直接访问:
unordered_set
提供了直接访问其元素的能力,这意味着你可以快速地遍历所有的单词,这对于动态规划中的子问题解决是非常有用的。 -
算法要求:在动态规划中,我们需要频繁地检查子串是否为
wordDict
中的单词。如果使用其他数据结构,如vector
或list
,查找操作的时间复杂度将是 O(n),这将大大降低算法的效率。 -
实现简单:
unordered_set
的使用非常简单,可以直接使用构造函数从vector
构造,不需要手动插入每个元素。
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// make words in wordDict store in unordered_set, for quick finding
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1, false); //need a extra room for check if dp[i] can be accessed from the back
dp[0] = true; // can be accessed from the back
}
};
接下来是写主代码逻辑了,矩阵虽然不用,但是矩阵永远都在。
就是说,i得确保整个string都被检查到,因为不确定string中的第几个字母组成的单词是可以在字典中被找到的,所以我直接穷举。这样不管第几个字母开头的几位数的单词,都确保在字典中被比对。
举例, 假设我得到一个 字符串s = "leetcode"
和 字典 wordDict = {"leet", "code"}。
观察到leetcode有8个字母,但是这里要设置一个9位数的string的长度,先别管为什么要多一位,直接暴力优雅学:
-
当
i = 1
时,检查s.substr(0, 1)
(即 "l"),它不在字典中,所以dp[1] = false
。 -
当
i = 2
时,检查s.substr(0, 2)
(即 "le"),它不在字典中,所以dp[2] = false
。 -
当
i = 3
时,检查s.substr(0, 3)
(即 "lee"),它不在字典中,但是s.substr(0, 1)
(即 "l")不在字典中,s.substr(1, 2)
(即 "ee")也不在字典中,所以dp[3] = false
。 -
当
i = 4
时,检查s.substr(0, 4)
(即 "leet"),它在字典中,所以dp[4] = true
。 -
当
i = 5
时,检查s.substr(0, 5)
(即 "leetc"),它不在字典中,但是s.substr(0, 4)
(即 "leet")在字典中,且s.substr(4, 1)
(即 "c")不在字典中,所以dp[5] = false
。 -
当
i = 6
时,检查s.substr(0, 6)
(即 "leetco"),它不在字典中,但是s.substr(0, 4)
(即 "leet")在字典中,且s.substr(4, 2)
(即 "co")不在字典中,所以dp[6] = false
。 -
当
i = 7
时,检查s.substr(0, 7)
(即 "leetcod"),它不在字典中,但是s.substr(0, 4)
(即 "leet")在字典中,且s.substr(4, 3)
(即 "cod")不在字典中,所以dp[7] = false
。 -
当
i = 8
时,检查s.substr(0, 8)
(即 "leetcode"),它不在字典中,但是s.substr(0, 4)
(即 "leet")在字典中,且s.substr(4, 4)
(即 "code")在字典中,所以dp[8] = true
。
又得学习一个新的用法,wordSet.find(s.substr(j, i - j))
尝试在 wordSet
中查找子串 s.substr(j, i - j)
。如果这个子串存在于 wordSet
中,find()
会返回一个指向该子串的迭代器。如果子串不存在,find()
会返回 wordSet.end()
。
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
// make words in wordDict store in unordered_set, for quick finding
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1, false); //need a extra room for check if dp[i] can be accessed from the back
dp[0] = true; // can be accessed from the back
// from 1,cuz dp[0] has been initialised true
for (int i = 1; i <= s.size(); ++i) {
for (int j = 0; j < i; ++j) {
// if the first j element can be scrambled,and the substring between j and i-1 of s is in wordSet
if (dp[j] && wordSet.find(s.substr(j, i - j)) != wordSet.end()) {
dp[i] = true;
break; // once finded, then break
}
}
}
return dp[s.size()];
}
};
今天的学习有点超标了,我不是很明白那个最后一段的 if 判断语句,我明天再看看,如果有新的理解,我会在这个基础上继续更新。