基础知识
题目
1.反转字符串 II( LeetCode 541 )
难度: 简单
题目表述:
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
代码(C++):
class Solution {
public:
void reverse(string& s, int l, int r) {
for (; l < r; l++, r--) swap(s[l], s[r]);
}
string reverseStr(string s, int k) {
int n = s.size();
for (int i = 0; i < n; i += 2 * k) {
reverse(s, i, min(i + k - 1, n - 1));
}
return s;
}
};
题解:
2.替换空格( 剑指Offer 05 )
难度: 简单
题目表述:
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
代码(C++):
class Solution {
public:
string replaceSpace(string s) {
int cnt = 0, n = s.size();;
for (int i = 0; i < n; i++) {
if (s[i] == ' ') cnt++;
}
s.resize(n + 2 * cnt);
for (int i = n - 1, j = s.size() - 1; i < j; i--, j--) {
if (s[i] != ' ') {
s[j] = s[i];
} else {
s[j--] = '0';
s[j--] = '2';
s[j] = '%';
}
}
return s;
}
};
题解:
3.反转字符串中的单词( LeetCode 151 )
难度: 中等
题目表述:
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
代码(C++):
class Solution {
public:
string reverseWords(string s) {
string res;
s = ' ' + s;
int j = s.size() - 1;
for (int i = s.size() - 1; i >= 0; i--) {
if (s[i] == ' ') {
if (i < j) res += s.substr(i + 1, j - i) + ' ';
j = i - 1;
}
}
return res.substr(0, res.size() - 1);
}
};
题解:
法1. 前面补空格,统一处理
法2. 先整体反转再局部反转(不使用辅助空间,空间复杂度要求为O(1)):
1)移除多余空格
2)将整个字符串反转
3)将每个单词反转
4.左旋转字符串( 剑指 Offer 58 - II )
难度: 简单
题目表述:
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。
代码(C++):
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(), s.begin() + n);
reverse(s.begin() + n, s.end());
reverse(s.begin(), s.end());
return s;
}
};
题解: 先局部反转再 整体反转
5.找出字符串中第一个匹配项的下标( LeetCode 28 )
难度: 中等
题目表述:
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
代码(C++):
class Solution {
public:
void getNext(int* next, const string& s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
while (j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if (s[i] == s[j + 1]) j++;
next[i] = j;
}
}
int strStr(string haystack, string needle) {
int next[needle.size()];
getNext(next, needle);
int j = -1;
for (int i = 0; i < haystack.size(); i++) {
while (j >= 0 && haystack[i] != needle[j + 1]) {
j = next[j];
}
if (haystack[i] == needle[j + 1]) j++;
if (j == needle.size() - 1) return i - needle.size() + 1;
}
return -1;
}
};
题解: KMP算法
6.重复的子字符串( LeetCode 459 )
难度: 简单
题目表述:
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
代码(C++):
class Solution {
public:
void getNext(vector<int>& next, const string& s) {
int j = -1;
next[0] = j;
for (int i = 1; i < s.size(); i++) {
while (j >= 0 && s[i] != s[j + 1]) {
j = next[j];
}
if (s[i] == s[j + 1]) j++;
next[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
vector<int> next(s.size());
getNext(next, s);
int len = next[s.size() - 1] + 1; // 最长相同前后缀的长度
if (len > 0 && s.size() % (s.size() - len) == 0) return true;
return false;
}
};
题解:
数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,如果这个周期可以被数组长度整除,就说明整个数组就是这个周期的循环。
小结
KMP算法
KMP应用: 匹配问题、重复子串问题
KMP思想: 当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
1. 构造next数组(前缀表 - 1)
前缀: 不包含最后一个字符的所有以第一个字符开头的连续子串;
后缀: 不包含第一个字符的所有以最后一个字符结尾的连续子串;
构造next数组 (计算模式串 j(包括j)之前的子串的相同前后缀的长度后 - 1,即找到前缀末尾位置的索引):
- 初始化 j = -1;
- 处理前后缀不相同的情况
前后缀不相同时,向前回退,即退到 j+1 前一个 j 的前缀末尾位置,再比较当前 i 和回退后 j + 1 是否相同,还是不同就继续回退 - 处理前后缀相同的情况
若相同就是找到了相同的前后缀,可以给next数组赋值为当前前缀末尾即,j++, next[i] = j;
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
2. 匹配
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
j++; // i的增加在for循环里
}
if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
return (i - t.size() + 1); // 返回第一个匹配模式串的文本串(主串)的起始位置
}
}