题目
给出 字符串 text 和 字符串列表 words, 返回所有的索引对 [i, j] 使得在索引对范围内的子字符串 text[i]...text[j](包括 i 和 j)属于字符串列表 words,(要求words里面的单词必须连续且不重复,并且要包含words的所有单词),返回其中的一组集合即可,此外对于返回的集合顺序没有要求
示例 1:
输入: text = "thestoryooooofleetcodeandme", words = ["story","fleet","leetcode"]
输出: []
ps: 没找到,单词匹配必须连续
示例 2:
输入: text = "thestoryfleetcodeandme", words = ["story","fleet","code"]
输出: [[3,7],[8,12],[13,16]] 或者 [[3,7],[13,16],[8,12]] 均可,输出无顺序要求
ps: 找到了一组恰好包含words里面的单词(单词不能重复使用,且要全使用words里面的单词)。此外 words 里的单词的顺序无所谓,只要不重复使用就行,并且对于输出的顺序无要求,上面两个答案均可
示例 3:
输入: text = "thestoryfleetcodeandme", words = ["story","fleet","leetcode"]
输出: []
ps: words里的单词不能重复使用,视为无效,words里面的单词必须连续且不重复
解题思路
方法一
暴力解法,生成words的全排列,然后和text取子串,这个解法没有什么意义,只是让大家了解题目
方法二
动态规划?
1、先找到从 text 里的第 0~text长度减一 的所有匹配 words 里面的单词,做个标记。比如 text[0] 能匹配words里面的某个单词,就记录下来,没有就不记录;text[3] 恰好能匹配 words 的一个单词,就记录下来,如果 text[3] 能匹配上很多 words 单词就都记录下来(极端情况下,如果words 里面的单词都比较像,比如 a、aa、aaa,是有可能匹配多个的),代码里我是用二维数组记录的,叫做 v_map,v_map 的x坐标长度由text的长度决定;v_map 的y坐标长度由实际匹配的words的单词决定,最长为 words 的长度
2、然后我们遍历第一步中的 v_map,从 v_map[0] 开始,尝试找出从text[0]开始,能否连续匹配words中的单词(对应下面代码的solve和外层for循环),我们用map存储命中的words的下标,map还能去重,如果map的size和words相等,那就找到结果了
代码
#include <iostream>
#include <map>
#include <vector>
using namespace std;
void solve(int i, string& text, vector<vector<int>>& v_map, map<int, int>& m, vector<string>& words, vector<vector<int>>& res) {
if (m.size() == words.size()) {
// 当 m 长度为 words 的大小时,说明全部匹配
res.clear();// 清理一下,以防万一
for (auto i : m) {
res.push_back({ i.second, i.second + (int)words[i.first].length() - 1 }); // 返回索引对集合
}
return;
}
if (i >= v_map.size()) {
// 不能越界
return;
}
for (auto j : v_map[i]) {
if (m.count(j) > 0) {
// 之前命中过,跳过
continue;
}
else {
// 命中,记录
m[j] = i;
solve(i + words[j].length(), text, v_map, m, words, res);
// 移除这次的 j (key值),防止影响下一次匹配(因为m是引用不是拷贝)
m.erase(j);
}
}
}
int main()
{
//string text = "thestoryfleetcodeandme";
//vector<string> words = { "story", "fleet", "code", "and" };
string text = "thestoryfoorfooandme";
vector<string> words = { "story", "foo", "foor", "and" };
int len = text.length();
vector<vector<int>> v_map(len); // 当 v_map 下标为 key 时, value 为命中(从v_map[key]开始的子串)对应在 words 的 下标集合数组,因为可能命中不止一个
// 构建 v_map
for (int i = 0; i < words.size(); i++) {
int pos = 0;
while (true) {
pos = text.find(words[i], pos);
if (pos != string::npos) {
v_map[pos].push_back(i);
}
else {
break;
}
pos += 1;
}
}
map<int, int> m; // key 为在 words 的下标,value 为该 words 的下标命中后,对应 text 的下标起始位置
vector<vector<int>> res; // res 为最终结果集
for (int i = 0; i < len; i++) {
// 每次遍历的目标是,从 text[i] 开始,接下来的连续的子串刚好命中 words 的所有单词,并且没有重复
solve(i, text, v_map, m, words, res);
// 匹配成功条件判定
if (res.size() == words.size()) {
// 匹配成功退出
break;
}
else {
// 从text[i]开始的连续索引对没有匹配成功,清空 m,准备下一次 for 循环,重新匹配
m.clear();
}
}
// 打印结果集
for (int i = 0; i < res.size(); i++) {
cout << '[';
for (int j = 0; j < res[i].size(); j++) {
cout << res[i][j];
if (j == 0) {
cout << ',';
}
}
cout << ']' << endl;;
}
return 0;
}