C C++算法数组字符串匹配
Junkman丶 人气:0题目描述
题目链接:1408. 数组中的字符串匹配
给你一个字符串数组 words
,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words
中是其他单词的子字符串的所有单词。
如果你可以删除 words[j]
最左侧和/或最右侧的若干字符得到 word[i]
,那么字符串 words[i]
就是 words[j]
的一个子字符串。
提示:
示例 1:
输入:words = ["mass","as","hero","superhero"]
输出:["as","hero"]
解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。
["hero","as"] 也是有效的答案。
示例 2:
输入:words = ["leetcode","et","code"]
输出:["et","code"]
解释:"et" 和 "code" 都是 "leetcode" 的子字符串。
示例 3:
输入: words = ["blue","green","bu"]
输出: []
整理题意
题目给定一个字符串数组 words
,对于数组中的每个字符串来说,如果该字符串为数组中其他某个字符串的子串,那么就将该字符串加入答案字符串数组。可以按照任意顺序返回该答案数组。
解题思路分析
注意题目的数据提示:题目数据 保证 每个 words[i]
都是独一无二的。所以不存在两个相同的字符串,也避免了互为子字符串的情况。
根据题目数据范围来看,完全可以采用较为暴力的方法来进行解题,枚举每个字符串作为子串,检查是否为其他某个字符串的子串即可。
优化
在字符串匹配的时候可以采用 KMP
字符串匹配算法来进行优化时间复杂度。
具体实现
对于字符串匹配部分可以调用 string
中的 find()
函数进行匹配 t.find(p)
(在字符串 t
中匹配字符串 p
,也就是查找字符串 t
中是否包含字符串 p
):
- 此处需要用到
string
库中的find()
函数与string::npos
参数;
string::npos
参数是一个常数,用来表示不存在的位置。
string
中find()
返回值是子串的第一个字符在母串中的位置(下标记录),如果没有找到,那么会返回一个特别的标记string::npos
。
可以对字符串数组 words
进行排序处理,这样就可以从最短的字符串开始匹配,且每次往后遍历匹配,因为前面的字符串一定短于当前字符串。
在使用 KMP
字符串匹配算法时需要注意:
KMP
字符串匹配算法的核心思想是 递归回溯思想,当匹配失败时根据nxt
数组来进行回溯跳转;nxt
数组表示模式串的子串的前缀和后缀相同的最长长度,这样就可以在匹配的过程中如果遇到不匹配的字符,模式串用nxt
数组进行递归跳转到最长符合的位置进行继续匹配,从而不需要目标串进行重复的往返匹配。- 其中需要要注意的一个技巧是
nxt[0] = -1
,在把nxt
数组进行向右偏移时,第0
位的值,我们将其设成了-1
,这只是为了编程的方便,并没有其他的意义。 - 还需要注意
nxt
数组的优化,优化后在回溯跳转的时候会回溯跳转到首次与当前字符不一样字符的位置,避免了跳转到和当前字符一样的位置进行重复判断。 - 在实现
getNext()
函数的时候需要注意nxt
数组溢出问题,可以通过增加nxt
数组大小,或减少getNext()
函数中循环遍历的次数来防止越界出现的运行错误。 - 需要注意在
getNext()
函数中j
的初始化为-1
,但在KMP()
函数中j
的初始化为0
。
复杂度分析
代码实现
暴力
class Solution { public: vector<string> stringMatching(vector<string>& words) { // 新知识:string::npos vector<string> ans; ans.clear(); // 双重循环暴力寻找 for(auto &word1 : words){ int l1 = word1.length(); for(auto &word2 : words){ int l2 = word2.length(); // 当 l2 大于 l1 时 并且可以在 w2 中找到 w1 时 if(l1 < l2 && word2.find(word1) != string::npos){ ans.emplace_back(word1); break; } } } return ans; } };
暴力 + 优化
class Solution { public: vector<string> stringMatching(vector<string>& words) { sort(words.begin(), words.end(), [](string &a, string &b){ return a.length() < b.length(); }); // 新知识:string::npos vector<string> ans; ans.clear(); int n = words.size(); // 双重循环暴力寻找 for(int i = 0; i < n; i++){ int l1 = words[i].length(); for(int j = i + 1; j < n; j++){ int l2 = words[j].length(); // 当 l2 大于 l1 时 并且可以在 w2 中找到 w1 时 if(l1 < l2 && words[j].find(words[i]) != string::npos){ ans.emplace_back(words[i]); break; } } } return ans; } };
KMP
class Solution { void getNext(string &p, vector<int> &nxt){ // 把PMT进行向右偏移时,第0位的值,我们将其设成了-1, // 这只是为了编程的方便,并没有其他的意义。 nxt[0] = -1; int i = 0, j = -1; int len = p.length(); // ★注意 nxt 数组越界 while(i < len){ // j = -1 或者 匹配成功 if(j == -1 || p[i] == p[j]){ // nxt[++i] = ++j; 未优化前 i++; j++; if(p[i] == p[j]) nxt[i] = nxt[j]; else nxt[i] = j; } // 匹配失败,回溯 else{ j = nxt[j]; } } } bool kmp(string &t, string &p, vector<int> &nxt){ // ★注意这里的 j = 0 不是 j = -1 int i = 0, j = 0; int lent = t.length(); int lenp = p.length(); while(i < lent && j < lenp){ if(j == -1 || t[i] == p[j]){ ++i; ++j; } else j = nxt[j]; } if(j == lenp) return true; return false; } public: vector<string> stringMatching(vector<string>& words) { sort(words.begin(), words.end(), [](string a, string b){ return a.length() < b.length(); }); vector<string> ans; ans.clear(); vector<int> nxt; int n = words.size(); for(int i = 0; i < n; i++){ int len_p = words[i].length(); // ★注意 nxt 数组溢出 // 可以这里 len_p + 1 也可以 getNext 中 -1 nxt.resize(len_p + 1); getNext(words[i], nxt); for(int j = i + 1; j < n; j++){ if(kmp(words[j], words[i], nxt)){ ans.emplace_back(words[i]); break; } } } return ans; } };
总结
- 通过该题了解到了一个新的知识点:
string::npos
参数用来表示不存在的位置。当string
中find()
函数没有匹配成功时,那么就会返回这个参数string::npos
。 - 同时通过该题复习了 KMP 字符串匹配算法 的实现,在实现过程中需要注意
nxt
数组的大小,防止下标越界的运行错误;同时还需要注意在getNext()
函数中j
的初始化为-1
,但在KMP()
函数中j
的初始化为0
。
测试结果:
加载全部内容