目录
344. 反转字符串
双指针
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size() / 2; i++, j--)
{
char tmp;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
};
541. 反转字符串 II
本题是将数组拆分成一个个2*k大小的小组,所以可以直接让i+2*k
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.begin() + i + k);
}else
{
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
剑指Offer 05.替换空格
题目链接:替换空格__牛客网
仍然使用双指针,可以先将数组扩充到替换成%20后需要的大小,之后从后往前遍历数组,双指针一个i指向扩充后数组末端,一个j指向原数组末端
class Solution {
public:
void replaceSpace(char *str,int length) {
int count = 0; //记录空格数
for (int i = 0;i < length; i++){
if (str[i] == ' ')
count++;
}
int length2 = length + count * 2; //扩充后数组长度
for (int j = length - 1, i = length2 - 1; j >= 0; j--, i--)
{
if (str[j] != ' ')
{
swap(str[j], str[i]);
}else
{
str[i--] = '0';
str[i--] = '2';
str[i] = '%';
}
}
}
};
151. 反转字符串中的单词
大体思路是可以先去除原字符串中所有多余的空格,之后再将整个字符串反转,最后将每个单词反转回来。
去除多余空格思路:
最好不要用erase来直接去掉空格,因为erase本身的时间复杂度就是O(n),再遍历一遍字符串之后复杂度将会变为O(n*n)了。
所以可以采用快慢指针的方法来做,这里去除空格的思路与之前数组题中的一道题一致:27. 移除元素
class Solution {
public:
void RemoveSpc(string &s)
{
int slow = 0; //慢指针:指向新数组的末尾
for (int fast = 0; fast < s.size(); fast++)
{
if (s[fast] != ' ')
{
if (slow != 0) //slow!=0说明此时不是第一个单词,所以要在其末尾补上一个空格
{
s[slow++] = ' ';
}
while (fast < s.size() && s[fast] != ' ')
{
s[slow++] = s[fast++];
}
}
}
s.resize(slow); //重新设置数组大小
}
void reverse(string &s, int start, int end)
{
for (int i = start, j = end; i < j; i++, j--)
{
swap(s[i], s[j]);
}
}
string reverseWords(string s)
{
RemoveSpc(s);
reverse(s, 0, s.size() - 1);
int start = 0; //记录一个单词的开始位置1
for (int i = 0; i <= s.size(); i++)
{
if (s[i] == ' ' || i == s.size()) //如果遇到空格则说明已经到了一个单词的结束位置,且若i到了数组的末尾,说明已经到了最后一个单词位置
{
reverse(s, start, i - 1);
start = i + 1; //更新
}
}
return s;
}
};
剑指Offer58-II.左旋转字符串
(找不到练习这题的地方qaq)
和上题一样可以通过局部反转以及全局反转来达成目的。
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;
}
};
28. 找出字符串中第一个匹配项的下标
KMP算法
class Solution {
public:
void getNext(int *next, string needle)
{
//初始化
int j = 0;
next[0] = 0;
for (int i = 1; i < needle.size(); i++)
{
//前后缀不匹配时
while (j > 0 && needle[i] != needle[j])
{
j = next[j - 1];
}
//前后缀匹配时
if (needle[i] == needle[j])
{
j++; //j++后可表示为前后缀相等的长度
}
//更新next
next[i] = j;
}
}
int strStr(string haystack, string needle)
{
if (needle.size() == 0)
{
return 0;
}
int next[needle.size()];
getNext(next, needle);
for (int i = 0, j = 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;
}
};
459. 重复的子字符串
KMP:
下方分别是原字符串s,最长相等前缀t,最长相等后缀k(按照下方的推理过程,这个后缀一定是从s中最后一个字符开始的,即应该是len-1这个位置的next)
最长相等前后缀不包含的子串就是最小重复子串,如下图s[0]s[1]
步骤1:因为t和k为最长相等前后缀,所以t[0] = k[0],所以根据上图位置可推知s[0] = s[2];又因为t[1] = k[1],所以s[1] = s[3],所以s[0]s[1] = s[2]s[3]
步骤2:因为t[2]和k[0],t[3]和k[1]在s中位置相等,所以可知t[2]t[3] = k[0]k[1] (不太懂这步的在推理中的意义)
步骤3:与步骤1同理,可知s[2]s[3] = s[4]s[5]
最后反复上诉步骤,得到s[0]s[1] = s[2]s[3] = s[4]s[5]
数学推导:
假设s由多个长度为x的n个子串组成,所以s的len = n * x
因为最长相等前后缀不包含的子串就是最小重复子串,所以最长相等前后缀的长度为m * x,且m = n + 1
所以可知若nx % (n - m)x = 0,就可以判定有重复出现的子字符串
如果 next[len - 1] != 0,则说明字符串有最长相同的前后缀(前缀是从s的第一个字符开始,后缀从s的最后一个字符开始)
所以如果len % (len - (next[len - 1])) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。
class Solution {
public:
void getNext(int *next, string needle)
{
int j = 0;
next[0] = 0;
for (int i = 1; i < needle.size(); i++)
{
while (j > 0 && needle[i] != needle[j])
{
j = next[j - 1];
}
if (needle[i] == needle[j])
{
j++;
}
next[i] = j;
}
}
bool repeatedSubstringPattern(string s)
{
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;
}
};