代码随想录算法训练营Day9 KMP
关于KMP
我的KMP是跟着一个老师学的。他做的可视化视频很好的解释了“怎么跳pi指针”,然后也给了一个板子。在暑期集训的时候,这个板子出现了一些边界问题——板子是0-base的,这就导致有些时候不如1-base的方便。我目前没有太好的解决方案。
Leetcode 151. 反转字符串中的单词
题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/
题目描述:给你一个字符串s,请你反转字符串单词的顺序。
单词是由非空格字符组成的字符串。s中使用至少一个空格将字符串中的单词分隔开。
返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。
注意:输入字符串s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 \(O(1)\) 额外空间复杂度的原地解法。
题目解析:这个题目如果不限制做法就太简单了,随便暴力一下就过了。我们这里只讨论“进阶”的解法。
由于是原地修改,我们需要先把单词的位置对应上。那么我们考虑把整个字符串进行反转。
整体的思路是实现三个功能:
- 实现字串的平移
- 反转所有的单词
- 删除多余的空格
举个例子。我当前的字符串s = " hello world "(在句子的两侧是有空格的),我们期望答案是s = "world hello",首先对应上位置,变成" dlrow olleh "。第一步,我们考虑把"dlrow"平移,覆盖掉他左侧的空格。第二步,我们考虑反转"dlrow",这个直接用库函数实现就行了。第三步,因为所有的空格都被“平移覆盖”了,那么最后多余的空格就全在右侧。
具体的实现就看代码吧。
class Solution {
public:
string reverseWords(string s) {
// 反转整个字符
std::ranges::reverse(s);
// n:字符串长度
// idx:新字符串的处理到哪一位了(覆盖到哪一位了)
int n = s.size(), idx = 0;
// 枚举所有可能的单词起点
for (int i = 0; i < n; i ++) {
// 如果不是空格,说明遇到单词了。
// 我们考虑反转单词。
if (s[i] != ' ') {
// 不是首个单词以外的单词都要前置空格
if (idx) s[idx ++] = ' ';
// 找到这个单词
int j = i;
while (j < n && s[j] != ' ') s[idx ++] = s[j ++];
// 反转单词
std::reverse(s.begin() + idx - (j - i), s.begin() + idx);
// 避免重新处理该单词
i = j;
}
}
s.erase(s.begin() + idx, s.end());
return s;
}
};
跑完整个代码,idx就是新字符串的长度。这里相当于是一个原地覆盖。
卡码网 55. 右旋字符串
题目链接:https://kamacoder.com/problempage.php?pid=1065
题目描述:字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
题目分析:同上题。我们考虑先把位置对应上来,然后反转对应的部分。
#include <bits/stdc++.h>
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;
using i128 = __int128;
using u128 = unsigned __int128;
using namespace std;
#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define Fd(i, a, b) for (int i = (a); i >= (b); i--)
#ifndef DEBUG
struct __X {
__X& operator<<(const auto& str) { return *this; }
void sp(const std::string& str = "") {}
} dout;
#define debug(x) ;
#endif
constexpr int mod = 998244353;
constexpr int MOD = 1E9 + 7;
constexpr int INF = 1E9;
constexpr int N = 2E5 + 10;
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int k;
std::string s;
std::cin >> k >> s;
std::reverse(s.begin(), s.end());
std::reverse(s.begin(), s.begin() + k);
std::reverse(s.begin() + k, s.end());
std::cout << s << '\n';
}
/* By Tangzy
⠄⠄⠄⠄⢠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣯⢻⣿⣿⣿⣿⣆⠄⠄⠄
⠄⠄⣼⢀⣿⣿⣿⣿⣏⡏⠄⠹⣿⣿⣿⣿⣿⣿⣿⣿⣧⢻⣿⣿⣿⣿⡆⠄⠄
⠄⠄⡟⣼⣿⣿⣿⣿⣿⠄⠄⠄⠈⠻⣿⣿⣿⣿⣿⣿⣿⣇⢻⣿⣿⣿⣿⠄⠄
⠄⢰⠃⣿⣿⠿⣿⣿⣿⠄⠄⠄⠄⠄⠄⠙⠿⣿⣿⣿⣿⣿⠄⢿⣿⣿⣿⡄⠄
⠄⢸⢠⣿⣿⣧⡙⣿⣿⡆⠄⠄⠄⠄⠄⠄⠄⠈⠛⢿⣿⣿⡇⠸⣿⡿⣸⡇⠄
⠄⠈⡆⣿⣿⣿⣿⣦⡙⠳⠄⠄⠄⠄⠄⠄⢀⣠⣤⣀⣈⠙⠃⠄⠿⢇⣿⡇⠄
⠄⠄⡇⢿⣿⣿⣿⣿⡇⠄⠄⠄⠄⠄⣠⣶⣿⣿⣿⣿⣿⣿⣷⣆⡀⣼⣿⡇⠄
⠄⠄⢹⡘⣿⣿⣿⢿⣷⡀⠄⢀⣴⣾⣟⠉⠉⠉⠉⣽⣿⣿⣿⣿⠇⢹⣿⠃⠄
⠄⠄⠄⢷⡘⢿⣿⣎⢻⣷⠰⣿⣿⣿⣿⣦⣀⣀⣴⣿⣿⣿⠟⢫⡾⢸⡟⠄.
⠄⠄⠄⠄⠻⣦⡙⠿⣧⠙⢷⠙⠻⠿⢿⡿⠿⠿⠛⠋⠉⠄⠂⠘⠁⠞⠄⠄⠄
⠄⠄⠄⠄⠄⠈⠙⠑⣠⣤⣴⡖⠄⠿⣋⣉⣉⡁⠄⢾⣦⠄⠄⠄⠄⠄⠄⠄⠄
*/
Leetcode 28. 找出字符串中第一个匹配项的下标
题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
题目描述:给你两个字符串haystack和needle,请你在haystack字符串中找出needle字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是haystack的一部分,则返回-1。
题目分析:KMP的板子题。最开始的视频也是用的这个作为讲解。
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m = needle.size();
std::string s = needle + '#' + haystack;
std::vector<int> pi(s.size());
for (int i = 1; i < s.size(); i ++) {
int len = pi[i - 1];
while (len && s[i] != s[len]) {
len = pi[len - 1];
}
if (s[i] == s[len]) {
pi[i] = ++ len;
if (pi[i] == m) {
return i - 2 * m;
}
}
}
return -1;
}
};
Leetcode 459. 重复的子字符串
题目链接:https://leetcode.cn/problems/repeated-substring-pattern/description/
题目描述:给定一个非空的字符串s,检查是否可以通过由它的一个子串重复多次构成。
题目分析:直接暴力枚举他的所有字串,然后看能不能构造出自身。这个思路显然是不优的。
我们需要考虑这个字符串的特殊性质。如果他由某种重复子串拼接,那么有$$s = s' + s'+s'+...+s'$$
我们可以想到,如果我们拼接把s的某一前缀移动到s的末尾(既包括删除前缀,也包括把这个前缀加到s的末尾),构造新的字符串str,而我们能够在str中找到s,就是true。
由于我们不知道要多长的前缀,直接\(str = s + s\),然后跑一个KMP就行了。
class Solution {
public:
bool repeatedSubstringPattern(string s) {
auto kmp = [&](string& pattern, string& target) -> bool {
int n = pattern.size(), m = target.size();
string str = pattern + '#' + target;
vector<int> pi(str.size());
for (int i = 1; i < str.size(); i++) {
int len = pi[i - 1];
while (len && str[i] != str[len]) {
len = pi[len - 1];
}
if (str[i] == str[len]) {
pi[i] = len + 1;
if (pi[i] == m) {
return true;
}
}
}
return false;
// 若要使用下面这个判据,需要修改传参,调用kmp(s, s)
// return pi[s.size() - 1] != 0 && n % (n - pi[s.size() - 1]) == 0;
};
string t = (s + s).substr(1, 2*s.size() - 2);
return kmp(t, s);
}
};
题解还有一种判据是:判断pi数组的最后一个元素能否被字符串长度n整除。姑且也放上来,但是差别不大。

浙公网安备 33010602011771号