算法设计与分析第三周作业
题目详情
本周所选题目:题目链接
题目大意为:给出一个字符串s和一个单词字符串集合words(单词长度全部一致),找出s中能在某个字符开始能够匹配words里所有单词的叠加(连接)的开始下标。
例子及解析如下:
1. 当输入为:
s = "barfoothefoobarman",
words = ["foo","bar"]
则因为从s的下标0处开始"bar","foo"分别对应了words的字符串,所以下标0被记录,
同理下标9开始的子字符串"foobar"也分别对应了words里的字符串,所以下标9倍记录。
即输出为[0, 9](用一个数组来表示)。
2. 当输入为
s = "abc",
words = ["d", "f"]
此时输出为一个空数组[]。
题目分析和算法的设计
如果使用一般的遍历方法(即对s和words的字符串的每个字符遍历)来求解的话,不仅时间复杂度不好,而且代码冗余。
所以不妨试一下使用words里的每个字符串来对s进行检索,如果发现有符合条件的s的子字符串,则记录开始下标。
算法详情:
(1)使用两个哈希表,一个用来存words里的所有字符串并以这些字符串为索引记录它们在words里的个数,另一个用来存匹匹配了的字符串的个数。
(2)算法涉及到两个循环,外循环为从头开始遍历s字符串,内循环为按照words里的每个字符串检索s字符串寻找与之匹配的子字符串。
(3)外循环退出条件为剩下的未遍历的字符个数小于words的全部字符串的总长度的值。因为剩下的子字符串的长度都已经小于words里所有字符串的连接而成的任一字符串的长度,显然,后面的肯定不会出现匹配的。
(4)内循环:
a. 以当前访问的s的字符开始取长度与words里字符串长度一致的s的子字符,如果该子字符串在words里,则以该子字符串为索引的哈希值加1,否则退出内循环。
b. 如果第二个哈希表的以该子字符串为索引的哈希值大于原有的words里的该字符串的个数,而内循环的次数为words的字符串的个数,这就说明此时一定存在一个words里的字符串没有出现在检索的s的子字符串中,即不匹配,此时退出内循环。
(5)如果上述内循环是因为遍历了words的所有字符串而退出循环的(即因为默认条件退出),此时由(4)可知,每个words里的字符串都被s中相邻的子字符串所匹配,即此时可记录此时对s进行遍历的开始的下标值。
(6)使用stl的map来作为哈希表,使用字符串函数substr(int startIndex, int length)来对s字符串取子字符串,该函数有两个参数,第一个参数为源字符串中开始截取的字符的下标,第二个参数为截取的字符串的长度。
代码详情
class Solution {
public:
/*
s = "barfoothefoobarman",
words = ["foo","bar"]
*/
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans;
// 空串或者空集合
if (words.empty() || s.empty()) return ans;
int wordsL = words.size();
int wordL = words[0].size();
// 使用map进行哈希
map<string, int> hash1;
for (auto word : words) {
hash1[word] ++;
}
// 若字符串中的字符长度小于一个单词的长度,即可退出循环
int sL = (int)s.size();
for (int i = 0; i <= sL - wordsL * wordL; i ++) {
map<string, int> hash2;
int j = 0;
// 每次截取一个单词的长度的字符串出来比较
for (; j < wordsL; j ++) {
string test = s.substr(i + j * wordL, wordL);
// 如果words里没有上面对应的字符串,则退出内循环, 否则把该单词放入hash2中
if (hash1.find(test) == hash1.cend()) break;
hash2[test] ++;
// 哈希表2中的某一字符串的个数大于原有的,则不符合退出该循环
if (hash2[test] > hash1[test]) break;
}
// 如果上述循环是由于j不满足条件退出的,说明此时能找到满足条件的字符串
if (j == wordsL) ans.push_back(i);
}
return ans;
}
};
思考与总结
此次作业主要涉及了如何合理使用哈希表(使用map构造)、重构算法思路(不需遍历源字符串和字符串集合里的字符串的每个字符),本次作业的难点也在如何重构算法思路上了,而算法中的两个判断是至关重要的,因为它们关系到之后能否在源字符串中找到相邻的匹配的子字符串。
还有就是外循环不需要遍历s的所有的字符,也是一种简单的优化吧。