344. 反转字符串
题目
思路:双指针
官方:
class Solution {
public:
void reverseString(vector<char>& s) {
int n = s.size();
for (int left = 0, right = n - 1; left < right; ++left, --right) {
swap(s[left], s[right]);
}
}
};
随想录:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
swap(s[i],s[j]);
}
}
注意
数组中双指针的停止:可以是left<s.length()/2,也可以是left<right
541. 反转字符串 II
这道题目耗时很长,原因主要是越界问题,以及最后剩余字符如何处理,根据测试用例一遍一遍改的,因为这种方法不能考虑周全。
启示:
本题对比上两道题目给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章
我的代码:
class Solution {
public:
string reverse( string s){
for(int i=0,j=s.size()-1;i<j;i++,j--)
{
swap(s[i],s[j]);
}
return s;
}
string reverseStr(string s, int k) {
// int j,i; 想写就地反转呢
// for( j=0;j<s.size();){
// for( i=j;i<j+k&&i<s.size();i++){
// swap(s[i],s[k-i-1]);
// }
// j=i+k-1;
if(s.size()<k)
return reverse(s);
string str="";
int i;
for( i=0;i<s.size();i+=(2*k)){
str+=reverse(s.substr(i,k)); //因为前面判断了s.size()一定大于k,所以这里肯定不会越界
if((i+2*k)<=s.size())
str+=s.substr(i+k,k);
}
if(i!=s.size()&&(i-k)<s.size()) //最后for循环跳出是i+2*k,跳回到最后剩余不到k的位置上,再将这些加到str上,前面没有i!=size()的判断,则用例:abcd这种就会是bacdcd多出来了
str+=s.substr(i-k);
return str;
}
//0123
};
改进点:
- 自己写反转函数,可以直接使用引用,传入开始,结尾的参数,不用设置str消耗了多余空间。空间复杂度为O(n),而官方直接引用就是O(1)
- 可以使用是string库函数中的反转函数:reverse(s.begin(),s.end())
- 使用截至字符串函数:substr(startpos, length);
官方代码:
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;
}
};
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/reverse-string-ii/solution/fan-zhuan-zi-fu-chuan-ii-by-leetcode-sol-ua7s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
剑指 Offer 05. 替换空格
官方思路:
本题目是将一个字符换成三个字符,使用char类型的数组,该数组长度为原来数组的三倍(最大全替换),最后再返回为真实长度的数组
我的代码:
class Solution {
public:
string replaceSpace(string s) {
string str="";
int j=0;
for(int i=0;i<s.size();i++){
if(s[i]==' '){
str+="%20";
continue; //这里可以用continue,也可以不用在下面用else
}
str+=s[i];//string可以直接加char类型的,效率更高
// str.push_back(s[i]);
}
return str;
}
};
看了随想录后自己写的
bug点:
最后返回截取的开始位置
冗余点:
其实可以先计算空格的个数,不用直接扩充三倍,见随想录
class Solution {
public:
string replaceSpace(string s) {
int n=s.size(),j;
s.resize(s.size()*3);
int i=s.size()-1;
for(j=n-1;j>=0;j--){
if(s[j]!=' ')
{
s[i--]=s[j];
}else{
s[i]='0';
s[i-1]='2';
s[i-2]='%';
i=i-3;
}
}
return s.substr(i+1);
}
};
随想录:双指针
精妙之处在于双指针的终止条件是i==j相等,因为扩充的是空格的个数,所以相当于i在追j,当追上之后说明空格已经被填满了,前面没有空格了,可以停止,而不是i>=0作为i停止条件。
class Solution {
public:
string replaceSpace(string s) {
int count = 0; // 统计空格的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
s.resize(s.size() + count * 2);
int sNewSize = s.size();
// 从后先前将空格替换为"%20"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] != ' ') {
s[i] = s[j];
} else {
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
};
知识点:
- char转成string类型方式:str.push_back©; str+=c;
- s.resize(newsize) //扩充s的大小到newsize
151. 颠倒字符串中的单词
我的思路:
使用vector存储单词,再逆序加到一个string上
bug点:
- 前面有空格和后面有空格的处理
- 最后一个单词str的处理
class Solution {
public:
string reverseWords(string s) {
vector<string> v;
string ss,str;
int k=0;
while(s[k]==' ')
k++;
for(int i=k;i<s.size();i++){
if(s[i]==' '&&s[i-1]!=' '){
// reverse(str.begin(),str.end());
v.push_back(str);
str="";
}else if(s[i]!=' '){ //如果直接写else则,因为上面if条件的判断还是会加入空字符
str+=s[i];
}
}
if(str!="")
v.push_back(str);//最后一个单词
for(int i=v.size()-1;i>0;i--){
ss+=v[i]+" ";
}
ss+=v[0];
return ss;
}
};
看了随想录后的
算法思路:
- 先移除空格
- 反转整个字符串
- 反转单个单词
bug点:
临界值问题:
最后一个元素的处理【单词】
- 产生临界值问题的原因:因为每次以一个标志来判断是否是满足条件的,这里是每个单词后面的空格为标志,所以特殊情况就是最后一个单词后面没有空格时怎么办
计算数组下标的问题:
- reverse(),erase()这类要通过数组下标来对数据做处理的库函数!
class Solution {
public:
//双指针移除会造成单词的次序改变!!!所以用王道的课后题删除数组元素方法二,使用相对位置来删除数组的元素,即插入排序的思想,k始终指向最后一个储存好的元素
string remove(string s){//引用会改变最后的字符大小吗,应该不会,因为要删除,所以字符大小会改变,不像reverse仅仅是反转,所以可以用&直接返回string
int k=0,j=0;
while(s[j]==' ')
j++;
for(int i=j;i<s.size();i++){
if((s[i]==' '&&s[i-1]!=' '&&i!=s.size()-1)||s[i]!=' ') //这里不需要判断i>0,虽然有s[i-1]但是在前面已经将最前面的空格跳过去了,所以i要么是字母,要么是新的空格; 如何去掉最后单词后面的空格,
{
s[k]=s[i];
k++;
}
}
if(s[s.size()-1]==' ')
return s.substr(0,k-1);
else return s.substr(0,k);
}
string reverseWords(string s) {
int j=0,pos;
s=remove(s);
reverse(s.begin(),s.end());
while(s[j]==' '){
j++;
}
pos=j;//记录空格后面的那个位置
for(int i=j;i<s.size();i++){
if(s[i]==' '){
reverse(s.begin()+pos,s.begin()+i);//反转上一个空格的下一个字符到本次空格的上一个字符之间的单词 ;只是反转了,但是空格却没消失,这里每次遇到空格后反转的是空格前面的一个单词,而且如果两个单词中间有多个空格,就会重复反转
pos=i+1;
}
}
reverse(s.begin()+pos,s.end()); //最后一个后面没有空格
// if(s[0]==' ')
// s.erase(0,1); //我懂了比如abc空空 倒数第二个空格就没覆盖住!
return s;
}
};
//难点如何消除空格,除了s.erase(),只能是覆盖;以及反转后最后一个单词后面如果没有空格,那么该单词将不能反转
//随想录的解决方法是:先消除多余的空格,再反转整个单词,再反转单个单词。
//如何消除空格?!!!这个和王道删除值等于x的那个题目一样,和移除元素那个力扣题目一样!!!!!所以这个题目是综合的,你要做的就是先理清题目的要求,再分解步骤,每个步骤对应一个小算法或者经验值思路,正确将步骤做好就可以!!!!
//还有就是每次的bug点你要记住才有效啊!!每次都是临界点!不能全部依靠力扣的测试去看,要用自己的脑子模拟!!
//不成熟的算法是漏洞百出,各种补,成熟的算法应该是清晰且整齐的,覆盖所有的情况
反思
关于删除数组元素:
- 使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。【随想录】
- 使用插入排序的思想
- 相对位置王道
- 双指针(这种会改变数组元素的位置)
关于删除空格:
随想录方法一:
随想录使用的是&,因为最后用了resize()重新定义了大小,所以可以用引用!如果不resize()则返回的还是原来的字符串大小
// 移除冗余空格:使用双指针(快慢指针法)O(n)的算法
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; //整体思想参考Leetcode: 27. 移除元素:https://leetcode-cn.com/problems/remove-element/
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的大小即为去除多余空格后的大小。
}
随想录方法:
使用了一个标志来看是否进入了单词区,我使用的是pos标记第一个单词字符位置,用i标志单词的最后一个字符,没有使用标志进入单词区。使用标志每次if都判断。我的方法和随想录的简介版本一样 随想录解法
关于删除空格中&问题:使用resize()解决
关于最后一个单词没有空格的问题:也是单独处理最后一个单词
// 版本一
class Solution {
public:
// 反转字符串s中左闭又闭的区间[start, end]
void reverse(string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
// 移除冗余空格:使用双指针(快慢指针法)O(n)的算法
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); // 重新设置字符串大小
}
}
string reverseWords(string s) {
removeExtraSpaces(s); // 去掉冗余空格
reverse(s, 0, s.size() - 1); // 将字符串全部反转
int start = 0; // 反转的单词在字符串里起始位置
int end = 0; // 反转的单词在字符串里终止位置
bool entry = false; // 标记枚举字符串的过程中是否已经进入了单词区间
for (int i = 0; i < s.size(); i++) { // 开始反转单词
if (!entry) {
start = i; // 确定单词起始位置
entry = true; // 进入单词区间
}
// 单词后面有空格的情况,空格就是分词符
if (entry && s[i] == ' ' && s[i - 1] != ' ') {
end = i - 1; // 确定单词终止位置
entry = false; // 结束单词区间
reverse(s, start, end);
}
// 最后一个结尾单词之后没有空格的情况
if (entry && (i == (s.size() - 1)) && s[i] != ' ' ) {
end = i;// 确定单词终止位置
entry = false; // 结束单词区间
reverse(s, start, end);
}
}
return s;
}
// 当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为:
/* 主函数简单写法
string reverseWords(string s) {
removeExtraSpaces(s);
reverse(s, 0, s.size() - 1);
for(int i = 0; i < s.size(); i++) {
int j = i;
// 查找单词间的空格,翻转单词
while(j < s.size() && s[j] != ' ') j++;
reverse(s, i, j - 1);
i = j;
}
return s;
}
*/
};
官方解答 双端队列 API方法
剑指 Offer 58 - II. 左旋转字符串
方法一(随想录):局部反转+整体反转
我写的:整体反转+局部反转
冗余点:可以先局部,再整体反转更简洁
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(),s.end());
reverse(s.begin(),s.begin()+s.size()-n);
reverse(s.begin()+s.size()-n,s.end());
return s;
}
};
随想录:
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;
}
};
方法二:使用额外空间s,累加
class Solution {
public:
string reverseLeftWords(string s, int n) {
string ss="";
for(int i=n;i<s.size();i++){
ss+=s[i];
}
for(int i=0;i<n;i++){
ss+=s[i];
}
return ss;
}
};
方法三:substr拼接
class Solution {
public:
string reverseLeftWords(string s, int n) {
return s.substr(n)+s.substr(0,n);
}
};
28.实现strstr()
KMP算法
459.重复的子字符串
暴力法:我写的。。。。。
class Solution {
public:
bool repeatedSubstringPattern(string s) {
//字符串匹配的问题,这里的规律就是循环出现,如果没有循环出现
if(s=="")
return false;
string str="";
int j;
for(int i=0;i<s.size();i++){
str+=s[i];
int n=str.size();
for(j=n;j<s.size();j=j+n){
if(s.substr(j,n)!=str){
break;
}
}
if(j==s.size()&&n!=s.size()) //当n为s.size()时,就不是子串了
return true;
}
return false;
}
};
滑动窗口
3. 无重复字符的最长子串
库函数
reseize
s.substr()
reverse