一、翻转字符串里的单词
1.题目:给你一个字符串 s
,请你反转字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
Ps:输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
2.思路:先删除多余的空格,然后把字符串整体反转,再把单词反转
3.注意:
(1)erase()本身的复杂度为O(n),如果在循环里面使用,时间复杂度将变为O(n²)
4.代码:
// 版本一
void removeExtraSpaces(string& s) {
int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
// 去掉字符串前面的空格
while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') {
fastIndex++;
}
for (; fastIndex < s.size(); fastIndex++) {
// 去掉字符串中间部分的冗余空格
if (fastIndex - 1 > 0
&& s[fastIndex - 1] == s[fastIndex]
&& s[fastIndex] == ' ') {
continue;
} else {
s[slowIndex++] = s[fastIndex];
}
}
if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格
s.resize(slowIndex - 1);
} else {
s.resize(slowIndex); // 重新设置字符串大小
}
}
// 版本二 将版本一精简一下
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
二、右旋转字符串
1.题目:字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
输入描述:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出描述:输出共一行,为进行了右旋转操作后的字符串。
2.思路:
3.注意:
(1)ACM 模式要加头文件和命名空间等。
(2)法一存在内存泄漏
4.代码:
法一:
#include <iostream>
using namespace std;
void swapChar(char &a, char &b)
{
char temp;
temp = b;
b = a;
a = temp;
}
int main()
{
int k;
string s;
cin >> k;
cin >> s;
int left,right;
if( k > s.size() )
return 0;
left = 0;
right = s.size()-k-1;
while(left<right)
{
swapChar(s[left], s[right]);
}
left = s.size()-k;
right = s.size()-1;
while( left < right)
{
swapChar(s[left], s[right]);
}
left = 0;
right = s.size() - 1;
while( left < right)
{
swapChar(s[left], s[right]);
}
cout << s <<endl;
return 0;
}
法二:使用 revers()
#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;
}
三、 实现 strStr()
1.题目:给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
2.思路:
3.注意:
(1)在找到首元素相同,继续判断后续元素是否相同时,注意不能从 needle 的第二个元素开始接着判断,需要避免 needle 是单个元素的字符串。
(2)当 needle 是空字符串时,返回 0 。
(3)本题是 KMP 经典题目,学习前缀表思想!
在后续使用前缀表时有三种方式:
① 前缀表原型 ② 前缀表元素整体减一 ③ 前缀表元素右移一位,首位为 -1
4.代码:
法一:无前缀表,暴力匹配
class Solution {
public:
int strStr(string haystack, string needle) {
bool find = false;
if( needle.size() > haystack.size() )
return -1;
for(int left = 0; left <= (haystack.size() - needle.size()); left++)
{
if( haystack[left] != needle[0] )
{
continue;
}
for(int i=0; i<needle.size(); i++)
{
if(haystack[left + i] != needle[i])
break;
if( i == ( needle.size() - 1 ) )
find = true;
}
if(find)
return left;
}
return -1;
}
};
法二:使用前缀表原型的方式
class Solution {
public:
void getNext(int* next, const string& s) {
int j = 0;
next[0] = 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;
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
vector<int> next(needle.size());
getNext(&next[0], needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if (j == needle.size() ) {
return (i - needle.size() + 1);
}
}
return -1;
}
};
补:前缀表统一减一
class Solution {
public:
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]
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
vector<int> next(needle.size());
getNext(&next[0], needle);
int j = -1; // // 因为next数组里记录的起始位置为-1
for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
j = next[j]; // j 寻找之前匹配的位置
}
if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动
j++; // i的增加在for循环里
}
if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
return (i - needle.size() + 1);
}
}
return -1;
}
};
四、重复的子字符串
1.题目:给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
2.思路:构造前缀表
3.注意:
4.代码:
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;
}
};