字符串
一、双指针法
(一)、用途
1. 字符串反转
2. 扩展或删除数组中部分元素
(二)、 典型例题
1. LeetCode344.反转字符串
题目
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题
来源:力扣(LeetCode) 链接:力扣
思路
-
设置两个指针分别指向字符串的头与尾;
-
相遇前互相交换值;
代码
class Solution { public: void reverseString(vector<char>& s) { char temp; int left=0, right=s.size()-1; while(left<right){ temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } };
2. LeetCode剑指Offer05. 替换空格
题目
请实现一个函数,把字符串 s
中的每个空格替换成"%20"。
思路
-
遍历一遍,记录空格数量,增加字符串至所需空间;
-
设置双指针分别指向原字符串末尾、增加后空间末尾;
-
从原字符串末尾向前遍历,将非空格元素直接赋值给后指针,两指针同时前移一个字符;
-
若遇空格,后指针赋值三个指定字符,前指针前移一个字符,后指针前移三个字符;
-
至前指针指向首元素结束;
代码
class Solution { public: string replaceSpace(string s) { int count_space = 0; for(int i=0; i<s.size(); i++){ if(s[i]==' ') count_space++; } int s_oldsize = s.size(); s.resize(s_oldsize + count_space*2); int s_newsize = s.size(); for(int i=s_oldsize-1, j=s_newsize-1; i>=0; i--, j--){ if(s[i]!=' ') s[j] = s[i]; else{ s[j--] = '0'; s[j--] = '2'; s[j] = '%'; } } return s; } };
二、反转系列
1. LeetCode541.反转字符串2
题目
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
-
如果剩余字符少于 k 个,则将剩余字符全部反转。
-
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样
来源:力扣(LeetCode) 链接:力扣
思路
-
以i控制外层循环,每次加
2*k
; -
内层中如果
i+k
未超出串长度,则将i
至i+k
反转; -
若超出,则将i至串结束反转;
代码
class Solution { public: string reverseStr(string s, int k) { for(int i=0; i<s.size(); i+=2*k){ if(i+k<s.size()){ for(int j=i; j<(i+k+i)/2; j++){ int temp = s[j]; s[j] = s[i+k-(j-i)-1]; s[i+k-(j-i)-1] = temp; } continue; } for(int j=i; j<(s.size()+i)/2; j++){ int temp = s[j]; s[j] = s[s.size()-(j-i)-1]; s[s.size()-(j-i)-1] = temp; } } return s; } };
缩减版
class Solution { public: string reverseStr(string s, int k) { int n = s.length(); for (int i = 0; i < n; i += 2 * k) { reverse(s.begin() + i, s.begin() + min(i + k, n)); } return s; } };
2. LeetCode151.翻转字符串里的单词
题目
给你一个字符串 s ,逐个翻转字符串中的所有 单词 。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
请你返回一个翻转 s 中单词顺序并用单个空格相连的字符串。
说明:
-
输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
-
翻转后单词间应当仅用一个空格分隔。
-
翻转后的字符串中不应包含额外的空格。
来源:力扣(LeetCode) 链接:力扣
思路
-
设置字符串向量
result_vector
存储字符串中所有单词,设置temp
变量用于识别每个单词; -
从开始遍历字符串,首先到达第一个非空格字符;
-
将遇空格前所有字符添加至
temp
; -
当遇到空格时,将
temp
添加至result_vector
,并将temp
清空,继续遍历进入3步骤至循环结束; -
循环结束后,若
temp
非空,将temp
添加至result_vector
; -
从后向前遍历
result_vector
,将每个单词按规则存储于新字符串中;
代码
class Solution { public: string reverseWords(string s) { vector<string>result_vector; string result; int i=0; string temp; while(s[i]==' ') i++; for(; i<s.size(); i++){ if(s[i] == ' '){ if(temp.size() != 0){ result_vector.push_back(temp); temp.clear(); } } else{ temp.push_back(s[i]); } } if(temp.size()!=0){ result_vector.push_back(temp); } for(i=result_vector.size()-1; i>=0; i--){ result += result_vector[i]; if(i!=0) result += ' '; } return result; } };
3. LeetCode剑指Offer 58-2.左旋转字符串
题目
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
来源:力扣(LeetCode) 链接:力扣
思路
-
将前n个字符反转;
-
将其余字符反转;
-
将整个字符串反转;
优点
无需额外空间
代码
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; } };
三、KMP算法
算法思想
当用双指针遍历两字符串至某字符出现不匹配时,可知小字符串指针之前的字符已经匹配,因此可根据小字符串最长相等前后缀信息跳转此指针(因为指针之前n个与开始n个相同,可默认为匹配成功),从而避免指针跳转至开始位置重新匹配;
字符串前缀:不包含最后一个字符的所有以第一个字符开头的连续子串。
算法步骤
-
若小字符串长度为
0
,直接返回匹配下标为0
; -
计算小字符串每个位置最长相等前后缀长度;
-
默认第一个元素最长相等前后缀为
0
,j
初始值设为0
为匹配的最长前后子串长度; -
以
i
变量遍历字符串,循环内若j
还可以向前跳转(即j>0
)且当前i
下标字符与跳转字符不等,则一直跳转至两者相等或j=0
,跳转位置为j
前面子串最长相等前后缀(即认为前面n个已经匹配成功); -
跳出循环有两种情况,即
j==0
,或者跳转位置与i下标值相同(存在相等前后缀),进行两者相等判断,若相等则可以在跳转位置已匹配字符基础上继续匹配(即j++
),由于跳转位置已经为最长相等前缀,则此时加1后同样成立; -
则此时
j
值即为下标i处最长相等前后缀,继续遍历至结束;
-
-
进行大字符串与小字符串匹配
-
设置
j
初值0为匹配的子串长度; -
以i变量遍历大字符串,若
j
下标小字符串值与i下标大字符串值不同且j可以前移(j>0),则一直跳转至两者相等或j=0
,跳转位置为j前面子串最长相等前后缀(即认为前面n个已经匹配成功); -
跳出循环有两种情况,即
j==0
,或者跳转位置与i
下标值相同(存在相等前后缀),进行两者相等判断,若相等则可以在跳转位置已匹配字符基础上继续匹配(即j++
),由于跳转位置已经为最长相等前缀,则此时加1后同样成立; -
判断小字符串是否匹配完毕,若完毕,则返回大字符串匹配起始位置;
-
-
若大字符串遍历结束前未返回值,则匹配失败,返回-1;
代码
class Solution { public: void Get_next(int *next, string &needle){ int j=0; next[0]=0; for(int i=1; i<needle.size(); i++){ while(j>0 && needle[j]!=needle[i]){ j = next[j-1]; } if(needle[j] == needle[i]){ j++; } next[i] = j; } } int strStr(string haystack, string needle) { if(needle.size() == 0) return 0; int next[needle.size()]; Get_next(next, 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; } };
典型例题
LeetCode459. 重复的子字符串
题目
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
解法一
思路
-
设置新字符串为此字符串重复2遍;
-
去除新字符串第一个与最后一个字符;
-
利用kmp算法进行新字符串与原字符串匹配;
-
若匹配成功(返回值非-1)则返回true,否则返回false;
-
当字符串由重复子串构成时,将整个字符串重复两遍,则中间部分(非单独的2个拼接部分)一定可以找到与原字符串匹配的子字符串;
-
当去除第一个与最后一个字符,相当于破坏匹配的第一个与最后一个可匹配的子字符串,若仍能在剩余字符中匹配到原字符串,则该串由重复子串构成;
代码
class Solution { public: void Get_next(int *next, string small_str){ int j=0; next[0] = 0; for(int i=1; i<small_str.size(); i++){ while(j>0 && small_str[j]!=small_str[i]){ j = next[j-1]; } if(small_str[j] == small_str[i]){ j++; } next[i] = j; } } int Get_substr_start(string big_str, string small_str){ if(small_str.size() == 0) return 0; int j=0; int next[small_str.size()]; Get_next(next, small_str); for(int i=0; i<big_str.size(); i++){ while(j>0 && big_str[i]!=small_str[j]){ j = next[j-1]; } if(small_str[j] == big_str[i]){ j++; } if(j == small_str.size()) return i-small_str.size()+1; } return -1; } bool repeatedSubstringPattern(string s) { string t = s+s; if(Get_substr_start(t.substr(1, t.size()-2), s)!=-1){ return true; } else return false; } };
解法二
思路
-
利用kmp算法计算字符串每个位置的最长相等前后缀;
-
数组长度减去最长相同前后缀的长度相当于是一个周期的长度,如果这个周期可以被整个字符串长度整除,就说明整个字符串就是这个周期子字符串的循环。
代码
class Solution { public: void Get_next(int *next, string &s){ int j=0; next[0] = 0; for(int i=1; i<s.size(); i++){ while(j>0 && s[j]!=s[i]){ j = next[j-1]; } if(s[j] == s[i]){ j++; } next[i] = j; } } bool repeatedSubstringPattern(string s) { int next[s.size()]; Get_next(next, s); if(next[s.size()-1]!=0 && s.size()%(s.size()-next[s.size()-1]) == 0) return true; else return false; } };