字符串
字符串的题目主要涉及的做法包括:双指针法、反转、KMP法
反转字符串
-
- 反转字符串
class Solution {
public:
void reverseString(vector<char>& s) {
int len = s.size();
char temp;
for(int i=0, j=len-1; i<len/2; i++,j--)
{
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
};
-
- 反转字符串2
这题就是要搞清楚逻辑就行了,下面是自己一开始写的,有点啰嗦
- 反转字符串2
class Solution {
public:
string reverseStr(string s, int k) {
int n = (s.size()%(2*k)==0)? (s.size()/(2*k)):(s.size()/(2*k)+1);
for(int i=0; i<n; i++)
{
int p1 = 2*i*k, p2;
//如果剩余字符小于k个
if(s.size()-p1 <k )
{
p2 = s.size()-1;
while(p1<=p2)
{
char temp = s[p1];
s[p1] = s[p2];
s[p2] = temp;
p1++;
p2--;
}
}
else if(s.size()-p1 >=k)
{
//总的有n组,第i+1组的起始索引是i*2k
p2 = 2*i*k+k-1;
while(p1<=p2)
{
char temp = s[p1];
s[p1] = s[p2];
s[p2] = temp;
p1++;
p2--;
}
}
}
return s;
}
};
其实可以更简洁一些,反转也可以用库函数reverse
class Solution {
public:
string reverseStr(string s, int k) {
for(int i=0; i<s.size(); i+=(2*k))
{
if(i+k>s.size())
{
reverse(s.begin()+i,s.end());
}
else
reverse(s.begin()+i, s.begin()+i+k);
}
return s;
}
};
替换数字
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
#include<iostream>
using namespace std;
int main() {
string s;
while (cin >> s) {
int count = 0; // 统计数字的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小
s.resize(s.size() + count * 5);
int sNewSize = s.size();
// 从后先前将空格替换为"number"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] > '9' || s[j] < '0') {
s[i] = s[j];
} else {
s[i] = 'r';
s[i - 1] = 'e';
s[i - 2] = 'b';
s[i - 3] = 'm';
s[i - 4] = 'u';
s[i - 5] = 'n';
i -= 5;
}
}
cout << s << endl;
}
}
翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词,并且去除多余的空格
移除元素用erase的话时间复杂度是O(n),所以用双指针来实现的话,时间复杂度只需要O(n),空间复杂度O(1)
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 removeExtraSpace(string&s) // 去除所有空格并在相邻元素之间添加空格,用快慢指针
{
int slow = 0;
for(int i=0;i<s.size();i++)
{
if(s[i]!=' ') // 遇到空格就删掉
{
if(slow!=0) // 不是第一个单词,在单词之前加空格
s[slow++]=' ';
while(i<s.size() && s[i]!=' ') // 补上该单词,遇到空格就说明单词结束了
s[slow++] = s[i++];
}
}
s.resize(slow); // 删除多余空格之后的大小
}
string reverseWords(string s) {
removeExtraSpace(s); // 第一步先把多余的空格删除
reverse(s,0,s.size()-1); // 第二步,把字符串整体翻转一下
int start=0;
for(int i=0;i<=s.size();i++) // start是当前单词的开始位置
{
if(i==s.size() || s[i]==' ') // 遇到空格或者结尾
{
reverse(s,start,i-1);
start=i+1;
}
}
return s;
}
};
上面的快慢指针操作很妙,数组或字符串中删除元素用双指针做是常见的。
右旋字符串
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
不能申请额外空间,只能在本串上操作。 这样的话只能使用两次反转,负负得正haha
#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()
-
- 找出字符串中第一个匹配项的下标
这道题使用的思想是KMP思想,KMP主要用在字符串匹配上:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
前缀表: 前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
- 找出字符串中第一个匹配项的下标
下面先用自己想的两层for循环的方法,匹配不上的话需要回退指针,击败100%:
class Solution {
public:
int strStr(string haystack, string needle) {
// 用三个指针,关键在于匹配不上需要回退
int a,b,c;
if(haystack.size()<needle.size())
return -1;
for(a=0; a<(haystack.size()-needle.size()+1);a++)
{
c=a;
for(b=0;b<needle.size();)
{
if(haystack[c]==needle[b])
{
c++;
b++;
}
else
break;
}
if(b==needle.size())
return c-b;
}
return -1;
}
};
用KMP算法的代码如下,prefix存放最长前缀长度
class Solution {
public:
void getPrefix(vector<int>& prefix , const string& needle) // 求前缀表
{
int j=0;
prefix[0] = 0;
for(int i=1; i<needle.size(); i++)
{
while(j>0 && needle[i]!=needle[j])
j = prefix[j-1];
if(needle[i]==needle[j])
j++;
prefix[i] = j;
}
}
int strStr(string haystack, string needle) {
if(needle.size()==0)
return 0;
// 用KMP算法
// 定义一个prefix
vector<int> prefix(needle.size(),0);
getPrefix(prefix,needle);
int j=0; // j是前缀表的索引,一旦匹配失败,j就赋值为prefix[j-1],这样j就指向了前缀的后一个
for(int i=0; i<haystack.size();i++)
{
while(j>0 && haystack[i]!=needle[j])
j = prefix[j-1]; // 回退
if(haystack[i]==needle[j])
j++;
if(j==needle.size())
return (i-needle.size()+1);
}
return -1;
}
};
判断字符串是否是由重复的字符串组成
-
- 重复的子字符串
第一种思路是移动匹配法,如果s是由重复的子字符串构成,则s+s中间一定可以找到一个s。这个方法的时间复杂度为O(n),但是考虑到Strstr()的时间复杂度最佳为O(m+n),如果用暴力法的话是O(m*n),该方法代码如下:
- 重复的子字符串
class Solution {
public:
bool repeatedSubstringPattern(string s) {
// 第一种方法,将两个s拼接起来,如果s是由重复的字串构成的,则两个s拼接起来之后中间一定能找到子串s
string t = s+s;
t.erase(t.begin());
t.erase(t.end()-1);
if(t.find(s)!=std::string::npos)
return true;
return false;
}
};
第二种方法是KMP算法,十分巧妙,可以注意到如果s是由重复子串构成的,那么s的最长前缀和最长后缀错开的部分就是这个重复子串,代码如下:
class Solution {
public:
// 实现获得前缀表
void getPrefix(vector<int>& prefix, const string& s)
{
prefix[0] = 0;
int j =0;
for(int i=1; i<s.size(); i++)
{
while(j>0 && s[i]!=s[j])
j = prefix[j-1];
if(s[i]==s[j])
j++;
prefix[i] = j;
}
}
bool repeatedSubstringPattern(string s) {
// 用KMP算法实现,可以证明最长前缀和最长后缀错开的部分就是这个重复的子字符串
if(s.size()==0)
return 0;
vector<int> prefix(s.size(),0);
getPrefix(prefix,s);
int len = s.size();
// asdfasdfasdf
// prefix形式为 0,0,0,0,1,2,3,4,5,6,7,8
if(prefix[len-1]!=0 && len%(len-(prefix[len-1]))==0) // 子字符串的长度其实就是len-(prefix[len-1])
return true;
return false;
}
};