letcode 分类练习 字符串 151.翻转字符串里的单词 卡码网:55.右旋转字符串 28. 实现 strStr 459.重复的子字符串
151.翻转字符串里的单词
这道题综合考察了字符串的操作,思路如下
- 先翻转整个字符串
- 再逐个翻转每个单词
- 移除元素(首部的空格,中部多余的空格,尾部空格),说到移除元素,我们立马想到双指针法,快指针指向我们遍历的元素,慢指针指向元素应该存放的位置
class Solution {
public:
string reverseWords(string s) {
reverse(s.begin(), s.end());
int i = 0;
while(i<s.size()){
if(s[i] != ' '){
//如果不是空格,说明是字母,我们找对应的这个单词,并reverse
int j = i;
while(s[j] != ' ' && j < s.size())j++;
reverse(s.begin() + i, s.begin() + j);
i = j;
}
else i++;
}
// 移除元素套路,快慢指针
int slow = 0; int fast = 0;
while(s[fast] == ' ')fast++;
for(;fast<s.size();fast++){
if(s[fast] != ' ' || (s[fast] == ' ' && fast + 1 < s.size() && s[fast+1] != ' '))s[slow++] = s[fast];
}
s.resize(slow);
return s;
}
};
卡码网:55.右旋转字符串
循环右移类题目,思路:右移n位, 就是将第二段放在前面,第一段放在后面。
先整体倒叙:
再进行子段倒叙:
即完成目标:
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int n;
string s;
cin >> n;
cin >> s;
int len = s.size(); //获取长度
reverse(s.begin(), s.end()); // 整体反转
reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n
reverse(s.begin() + n, s.end()); // 再反转后一段
cout << s << endl;
}
28. 实现 strStr
KMP 解法
- 匹配过程
在模拟 KMP 匹配过程之前,我们先建立两个概念:
前缀:对于字符串 abcxxxxefg,我们称 abc 属于 abcxxxxefg 的某个前缀。
后缀:对于字符串 abcxxxxefg,我们称 efg 属于 abcxxxxefg 的某个后缀。
然后我们假设原串为 abeababeabf,匹配串为 abeabf:
首先匹配串会检查之前已经匹配成功的部分中里是否存在相同的「前缀」和「后缀」。如果存在,则跳转到「前缀」的下一个位置继续往下匹配:
跳转到下一匹配位置后,尝试匹配,发现两个指针的字符对不上,并且此时匹配串指针前面不存在相同的「前缀」和「后缀」,这时候只能回到匹配串的起始位置重新开始:
next 数组的构建
最终代码如下:
class Solution {
public:
int strStr(string s, string p) {
int n = s.size(), m = p.size();
if(m == 0) return 0;
//设置哨兵
s.insert(s.begin(),' ');
p.insert(p.begin(),' ');
vector<int> next(m + 1);
//预处理next数组
for(int i = 2, j = 0; i <= m; i++){
while(j and p[i] != p[j + 1]) j = next[j];
if(p[i] == p[j + 1]) j++;
next[i] = j;
}
//匹配过程
for(int i = 1, j = 0; i <= n; i++){
while(j and s[i] != p[j + 1]) j = next[j];
if(s[i] == p[j + 1]) j++;
if(j == m) return i - m;
}
return -1;
}
};
时间复杂度:n 为原串的长度,m 为匹配串的长度。复杂度为 O(m+n)。
空间复杂度:构建了 next 数组。复杂度为 O(m)。
来源:宫水三叶
459.重复的子字符串
移动匹配
当一个字符串s:abcabc,内部又重复的子串组成,那么这个字符串的结构一定是这样的:
也就是又前后又相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前后的子串做后串,就一定还能组成一个s,如图:
所以判断字符串s是否有重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是又重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
代码如下:
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t = s + s;
t.erase(t.begin()); t.erase(t.end() - 1); // 掐头去尾
if (t.find(s) != std::string::npos) return true; // r
return false;
}
};
这里用到了t.find(s) != std::string::npos来实现模式匹配,如果要实现一个高效的算法来判断一个字符串中是否出现另一个字符串是很复杂的,这里就涉及到了KMP算法。
class Solution {
public:
void getNext (int* next, const string& s){
next[0] = 0;
int j = 0;
for(int i = 1;i < s.size(); i++){
while(j > 0 && s[i] != s[j]) {
j = next[j - 1];
}
if(s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
bool repeatedSubstringPattern (string s) {
if (s.size() == 0) {
return false;
}
int next[s.size()];
getNext(next, s);
int len = s.size();
if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {
return true;
}
return false;
}
};
来源:代码随想录