1. 反转字符串
思路: 可以利用reverse函数直接反转,也可使用双指针从边界循环交换元素进行反转
class Solution {
public:
void reverseString(vector<char>& s) {
int l = 0 , r = s.size() - 1;
while (l < r)
swap(s[l++],s[r--]);
}
};
时间复杂度: O(n)
2. 反转字符串Ⅱ
leetcode链接
思路: 判断剩余字符串的长度,进行不同的反转字符串的操作
class Solution {
public:
string reverseStr(string s, int k) {
int length = s.size(); // 记录字符串剩余长度
// index记录当前操作的起始下标
int index = 0,l = 0,r = length -1;
while (length > 0) {
l = index;
if (length >= 2 * k)
r = index + k - 1;
else if (length >= k)
r = index + k - 1;
else
r = s.size() - 1;
while (l < r)
swap(s[l++], s[r--]);
length -= 2 * k;
index += 2 * k;
}
return s;
}
};
时间复杂度: O(n)
3. 替换数字
题目链接
思路: 循环遍历,将数字进行替换即可。若使用空间扩充,则不需要开辟辅助数组。若对新字符串从后往前进行替换,则在O(n)的时间复杂度即可完成,而从前往后进行替换,每次需向后移动字符,需O(n²)的复杂度。
#include<iostream>
using namespace std;
int main(){
string s;
while(cin >> s){
int count = 0; // 统计数字字符的数量
int index = s.size() - 1 ;
for(int i = 0; i < s.size();i++){
if(s[i] >= '0' && s[i] <= '9')
count++;
}
// 将s进行扩充
s.resize(s.size() + 5 * count);
int newIndex = s.size() - 1;
while(newIndex >= 0){
if(s[index] >= '0' && s[index] <= '9'){
s[newIndex--] = 'r';
s[newIndex--] = 'e';
s[newIndex--] = 'b';
s[newIndex--] = 'm';
s[newIndex--] = 'u';
s[newIndex--] = 'n';
}
else
s[newIndex--] = s[index];
index--;
}
cout << s << endl;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
4.反转字符串中的单词
leetcode链接
思路: 先去除多余空格,将字符串转变为标准的字符串。然后将整个字符串反转,再将每个单词反转,即可得到符合条件的字符串。
去除多余空格: 使用双指针的方法去除,不会开辟新的空间
版本一:使用一个布尔变量标记是否需要添加空格,然后最后修正字符串长度。
void removeExtraSpaces(string &s) {
// 定义快慢指针,去除多余空格
int fast = 0, slow = 0;
// 表示是否需要添加空格 ,默认为false可去除串首的空格
// 若字符串末尾有空格,则末尾会保留一位空格
bool need = false;
for (; fast < s.size(); fast++) {
if (s[fast] != ' ') {
s[slow++] = s[fast];
need = true;
continue;
}
if (need) {
s[slow++] = ' ';
need = false;
}
}
// 重设字符串大小
if (s[slow - 1] == ' ')
s.resize(slow - 1);
else
s.resize(slow);
}
版本二:也是双指针的方法移除空格,去除所有空格,然后在单词间手动添加一个空格
void removeExtraSpaces(string &s) {
int slow = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] != ' ') { // 当碰到单词开头时
if (slow != 0) // 且不为第一个单词时
s[slow++] = ' '; // 手动添加一个空格,保证每个单词间仅有一个空格
while (s[i] != ' ' && i < s.size()) // 将后续单词写入由慢指针写入
s[slow++] = s[i++];
}
}
s.resize(slow);
}
总体代码为:
class Solution {
public:
void reverse(string &s, int start, int end) { // 翻转函数(选定范围的翻转)
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string &s) {
int slow = 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] != ' ') { // 当碰到单词开头时
if (slow != 0) // 且不为第一个单词时
s[slow++] = ' '; // 手动添加一个空格,保证每个单词间仅有一个空格
while (s[i] != ' ' && i < s.size()) // 将后续单词写入由慢指针写入
s[slow++] = s[i++];
}
}
s.resize(slow);
}
string reverseWords(string s) {
removeExtraSpaces(s);// 移除多余空格
reverse(s, 0, s.size() - 1); // 翻转字符串
int start = 0; // 每个单词的起始下标
for (int i = 0; i <= s.size(); i++) {
if (i == s.size() || s[i] == ' ') {
reverse(s, start, i - 1);
start = i + 1; // 更新start
}
}
return s;
}
};
时间复杂度: O(n)
空间复杂度: O(1)
5. 右旋转字符串
思路1: 可创建一个与输入字符串同等长度的字符数组,将字符按要求填入新的字符数组,空间复杂度为O(n)
#include <iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
void reverse(string &s,int start ,int end) {
for (int i = start,j = end; i < j; i++, j--)
swap(s[i], s[j]);
}
int main() {
int k;
string s;
cin >> k;
cin >> s;
reverse(s, 0, s.size() - 1);
reverse(s, 0, k - 1);
reverse(s, k, s.size() - 1);
cout << s;
}
思路2: 不开辟新的空间,在原字符串上进行操作。先将整体字符串进行反转,在根据k的值将字符串分为两部分分别再反转,即可得到所需字符串。
#include <iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
void reverse(string &s,int start ,int end) {
for (int i = start,j = end; i < j; i++, j--)
swap(s[i], s[j]);
}
int main() {
int k;
string s;
cin >> k;
cin >> s;
reverse(s, 0, s.size() - 1);
reverse(s, 0, k - 1);
reverse(s, k, s.size() - 1);
cout << s;
}
时间复杂度: O(n)
空间复杂度: O(1)
6. 实现strStr()
思路: KMP算法,实现next数组与匹配字符串
next数组实现: 本题使用最原始的next数组,即next[i]表示 i 以前(包括i)的最长公共前后缀
用 j 指向前缀的末尾,用 i 指向后缀的末尾,匹配 i 的next值
A1与A2为 i - 1 最长公共前后缀,求 i 的最长前后缀
当 i 与 j指向的元素匹配时,next[ i ] = next[ i - 1 ] (即A1的 长度) + 1
// 当前前后缀相匹配(i与j前的k位匹配的字符串),则新的公共前后缀为k+1
if (s[i] == s[j]) {
next[i] = j + 1;
i++;
j++;
}
当不匹配时, 将指针j移动到A1部分的公共前后缀的位置,即B1后的位置,递归循环此操作,直到匹配成功或j == 0
if (j > 0)
j = next[j - 1];
else {
next[i] = 0;
i++;
}
字符串匹配的过程: 两指针j 和 i分别指向匹配串和原串,依次循环匹配并移动两指针。当两指针不相匹配时 , 根据next[ j ] 的值移动 j 指针,直接匹配成功或遍历完整个原串。
整体代码:
class Solution {
public:
void getnext(vector <int> &next,const string &s) {
// next[i]表示i以前(包括i)的最长匹配前后缀
next[0] = 0;
int j = 0, i = 1; // j表示前缀的尾指针,i表示后缀的尾指针
while (i < s.size()) {
// 当前前后缀相匹配(i与j前的k位匹配的字符串),则新的公共前后缀为k+1
if (s[i] == s[j]) {
next[i] = j + 1;
i++;
j++;
}
// 当前位置不匹配时,比较更短距离的前后缀
else {
if (j > 0)
j = next[j - 1];
else {
next[i] = 0;
i++;
}
}
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) // 匹配串长度为0,返回0
return 0;
vector <int> next(needle.size());
getnext(next, needle); // 获取next数组
int i = 0, j = 0; // i指向原串,j指向匹配串
while (i < haystack.size()) {
if (haystack[i] == needle[j]) { // 当当前位置匹配时,向后移动两指针
i++;
j++;
}
else { // 若不匹配时
if (j > 0)
// j移动到失配字符前一位的公共前后缀的后一位,与当前i指向的元素进行比较
j = next[j - 1];
else // 若循环到匹配串起点也不匹配时,向后移动i
i++;
}
if (j == needle.size()) // 匹配串匹配成功时,直接返回
return i - j;
}
return -1;
}
};
时间复杂度: O(n + m)
空间复杂度: O(m)
7. 重复的子字符串
思路1: 移动匹配,通过双倍字符串s+s中找原字符串s,判断s是否有某一子字符串构成。
必要性证明: 当s有某一子串重复构成,则可在双倍字符串中找到s
- 假设 s 可由 子串 x 重复 n 次构成,即 s = nx
- 则有 s+s = 2nx
- 移除 s+s 开头和结尾的字符,变为 (s+s)[1:-1],则破坏了开头和结尾的子串 x
- 此时只剩 2n-2 个子串
- 若 s 在 (s+s)[1:-1] 中,则有 2n-2 >= n,即 n >= 2
- 即 s 至少可由 x 重复 2 次构成
- 否则,n < 2,n 为整数,只能取 1,说明 s 不能由其子串重复多次构成
充分性证明: 当双倍字符串可找到s时,则s由某一子串重复构成。
图中,因为中间拼接成了s,根据红色框 可以知道 s[4] = s[0], s[5] = s[1], s[0] = s[2], s[1] = s[3] s[2] = s[4] ,s[3] = s[5];说明这个字符串,是由 两个字符 s[0] 和 s[1] 重复组成的!
s的其他组成方式同理也可推断出其由子串重复组成
因此,必要性与充分性都得到证明。
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string t = s + s;
t.erase(t.begin());
t.erase(t.end()- 1 );
//如果查找成功,则返回子串位置的值(起始位置);
// 如果查找失败,则返回特殊值 string::npos,表示没有找到。
if (t.find(s) != string::npos)
return true;
else
return false;
}
};
时间复杂度: O(n)
空间复杂度: O(n)
思路2: 使用KMP算法,判断s是否存在公共前后缀。若s由子串重复组成,s的最长公共前后缀的剩余部分即为符合条件的子串。
充分性与必要性的证明:代码随想录
class Solution {
public:
void getnext(vector <int> &next, const string &s) {
// next数组 从0开始的
next[0] = 0;
int j = 0, i = 1; // j为前缀尾指针,i为后缀尾指针
while (i < s.size()) {
if (s[j] == s[i]) {
next[i] = j + 1;
j++;
i++;
}
else {
if (j > 0)
j = next[j - 1];
else
next[i++] = 0;
}
}
}
bool repeatedSubstringPattern(string s) {
if (s.size() == 0)
return false;
vector <int> next(s.size(), 0);
getnext(next, s);
int len = s.size();
// 当s存在公共前后缀,且公共前后缀的剩余部分 的整数倍为 字符串的长度
if (next[len - 1] != 0 && len % (len - next[len - 1]) == 0)
return true;
else
return false;
}
};
时间复杂度: O(n)
空间复杂度: O(n)