一、字符串之反转
1.344 反转字符串
- 反转字符串:
-
reverse(str.begin(), str.end());
- 反转数组:
-
int a[5] = {1, 2, 3, 4, 5};
reverse(a, a+5);//n为数组中的元素个数这题的本意当然是不用库函数,怎么做:双指针思想
class Solution {
public:
void reverseString(vector<char>& s)
{
int length=s.size();
for(int i=0,j=length-1;i<length/2;i++,j--)
{
swap(s[i],s[j]);
}
}
};
总结:
- 字符串的长度:s.length();
- 字符数组的长度:s.size();
- 利用string头文件中的strlen()函数:strlen(str)
- C++的strlen(str)和str.length()和str.size()都可以求字符串长度。其中str.length()和str.size()是用于求string类对象的成员函数,strlen(str)是用于求字符数组的长度,其参数是char*。
- 这题里面的str是放在了vector字符数组里面,所以用size();
2.541 反转字符串 II
难度:简单
class Solution {
public:
string reverseStr(string s, int k)
{
int length=s.length();
for(int j=0;j<length;j+=(2*k))//j=0,2k,4k....
{
if(length-j<k)//如果剩余字符少于 k 个,则将剩余字符全部反转。
{
reverse(s.begin()+j,s.end());
}
else if((length-j<2*k)&&(length-j>=k))
//如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
{
reverse(s.begin()+j,s.begin()+j+k);
}
else
{
reverse(s.begin()+j,s.begin()+j+k);
}
}
return s;
}
};
总结:
-
begin
解释:begin()函数返回一个迭代器,指向字符串的第一个元素. -
end
解释:end()函数返回一个迭代器,指向字符串的末尾(最后一个字符的下一个位置).
二、字符串之双指针
1.剑指 Offer 05 替换空格:请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
- 方法一:replace
找有多少个空格,在有空格的位置用replace
class Solution {
public:
string replaceSpace(string s)
{
for (int i = 0; i < s.length(); i++)
{
if (s[i] == ' ')
{
s.replace(i, 1, "%20");
}
}
return s;
}
};
- 方法二:双指针
首先扩充数组到每个空格替换成"%20"之后的大小。然后从后向前替换空格,也就是双指针法,j指向新长度的末尾,i指向旧长度的末尾。
问题:从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
(1)不用申请新数组。
(2)从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
class Solution {
public:
string replaceSpace(string s)
{
int size=s.length();
int count=0;
for(int i=0;i<s.length();i++)
{
if(s[i]==' ')
{
count++;//有多少空格
}
}
s.resize(2*count+size,' ');//扩充
int size_new=s.length();
for(int i=size-1,j=size_new-1;i<j;i--,j--)//j是新字符串的尾端
{
if(s[i]!=' ')
{
s[j]=s[i];
}
else
{
s[j]='0';
s[j-1]='2';
s[j-2]='%';
j=j-2;
}
}
return s;
}
};
三、字符串之匹配KMP算法
举个例子:要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
-
先在模式串里面找连续子串。
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
在这些子串里面找最长相等前后缀。 -
得到前缀表(前缀表里面放的是最长相等前后缀的长度),前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
子串 | 最长相同前后缀的长度 |
---|---|
a | 0 |
aa | 1 |
aab | 0 |
aaba | 1 |
aabaa | 2 |
aabaaf | 0 |
- 找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
比如aabaaf里面不匹配的位置是f,他前一个字符的前缀表的数值是2,所以把下标移动到文本串下标2的位置继续比配。
最后就在文本串中找到了和模式串匹配的子串了
代码:
- 先构造next数组:
(1)初始化:定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j),所以next[0]=j;
int j = -1;
next[0] = j;
(2)处理前后缀不相同的情况:如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
怎么回退呢?next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
(3)处理前后缀相同的情况:如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
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要保证大于0,因为下面有取j-1作为数组下标的操作
j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if (s[i] == s[j]) {
j++;
}
next[i] = j;
}
}
- 使用next数组来做匹配
class Solution
{
public:
void getNext(int* next, const string& s)
{
int j = 0;
next[0] = 0;
for(int i = 1; i < s.size(); i++) //注意i从1开始
{
while (j > 0 && s[i] != s[j]) //前后缀不相同了
{
j = next[j - 1];
}
if (s[i] == s[j]) // 找到相同的前后缀
{
j++;
}
next[i] = j;// 将j(前缀的长度)赋给next[i]
}
}
//下面用next数组做匹配
int strStr(string haystack, string needle)
{
if (needle.size() == 0)
{
return 0;
}
int next[needle.size()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) // 注意i就从0开始
{
while(j > 0 && haystack[i] != needle[j])
{
j = next[j - 1];
}
if (haystack[i] == needle[j])
{
j++;
}
if (j == needle.size() )// 文本串s里出现了模式串t
{
return (i - needle.size() + 1);
}
}
return -1;
}
};