题目链接,有点像判断回文串的思路
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i=0,j=s.size()-1;i<s.size()/2;i++,j--){
//i<s.size()/2不用加一,因为数组是从0开始的,刚刚好
swap(s[i],s[j]);
}
}
};
- 反转字符串II
每2k个字符重置其前k个。还是采用双指针,在for循环里i控制2k的移动,j控制2k里的前k个即i+k-1?
这个稍微复杂一点可以用reverse函数,就是注意reverse的范围,如果需要从i开始,
我还以为得-1呢,用begin()指向的数组元素的第一个位置,而end()指向的是数组的最后一个元素的下一个位置!!!
但是i是从0开始的,所以加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()){
reverse(s.begin()+i,s.begin()+i+k);
}
else{
reverse(s.begin()+i,s.end());
}
}
return s; }
};
自己设计reverse 函数
class Solution {
public:
void reverseString(string &s,int start,int end) {
for(int i=start,j=end;i<j;i++,j--){
//i<s.size()/2不用加一,因为数组是从0开始的,刚刚好
swap(s[i],s[j]);
}
}
string reverseStr(string s, int k) {
for(int i=0;i<s.size();i+=2*k){
if(i+k<=s.size()){
reverseString(s,i,i+k-1);
continue;
}
else{
reverseString(s,i,s.size()-1);
}
}
return s; }
};
跟反转字符串的题目里自己设计的函数差不多,但是唯一的区别是循环条件是i>j,这里可不能用size()了
用while循环如何实现
class Solution {
public:
string reverseStr(string s, int k) {
int i=0;
while(i<s.size()){
if(i+k<s.size())reverse(s.begin()+i,s.begin()+i+k);
else reverse(s.begin()+i,s.end());
i+=2*k;
}
return s; }
};
题目链接
对于线性数据结构,填充或者删除,后序处理会高效的多
方法一,利用额外的数组,当原数组遇到了空格,就在新数组上加入%20,否则照搬下来
字符得用单引号而不是双引号
class Solution {
public:
string replaceSpace(string s) {
string p;
int j=0;
for(int i=0;i<s.size();i++){
if(s[i]==' ')p.push_back('%20');
//字符得用单引号而不是双引号
else p.push_back(s[i]);
}return p;
}
};
上面那个代码有个错误,就是push_back每次只能插入一个字符,且单个字符必须用单引号引起来
如果要追加可以用append(“”)
双引号引起来
或者用string的+=来进行一个连接
class Solution {
public:
string replaceSpace(string s) {
string p;
int j=0;
for(int i=0;i<s.size();i++){
if(s[i]==' ')p.append("%20");
//字符得用单引号而不是双引号
else p.push_back(s[i]);
}return p;
}
};
insert 会自动将元素的位置后移,但是这个题的时间复杂度控制的很紧
第二种方法就是 不用额外的辅助空间
其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。
这么做有两个好处:
不用申请新数组。
从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。
//也就是说呢,你没有一个数组来存这些初始的字符串,是在原字符串的基础上来进行一个改动,那么从前面开始替换肯定要让后面的原有的字符的位置
因此从后面往前刚好能给这个往后移动,反而不用再多余的移动还未遍历的元素了。
多想想这个过程,从排队的从前面往后移和从后面就开始为前面的移动,那么前面的不久不用再移动了。。。
首先扩充数组到每个空格替换成"%20"之后的大小。
从后往前,把空格替换成后移后的字符串。i指向新长度的末尾,j指向旧长度的末尾
从后添加元素,避免了从前面添加元素得将元素后移 的问题
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
string replaceSpace(string s) {
int count=0;
for(int i=0;i<s.size();i++){
if(s[i]==' ')count++;//比较相等用两个等号而不是用一个=
}
int n=s.size();
//这个resize的函数由于是容器函数,所以不用再标识对象了,而是直接写大小
s.resize(s.size()+count*2);
//这个string 算是一个容器,用相应的stl的容器的函数的时候,我们就可以
//直接想到用这个s.函数
//又学了一个新的函数,resize,重新改变数组的大小
for(int i=s.size()-1,j=n-1;i>j;i--,j--){
// for(int i=s.size()-1,j=n-1;i>=0&&j>=0;j--,i--){
if(s[j]==' ')
{s[i]='0';
s[i-1]='2';
s[i-2]='%';
i-=2;//可以之前用自增和自减
}else s[i]=s[j];
}
return s;
}
};
那个循环的条件要也可以写成这个样子,就是i>=0,j>=0
for(int i=s.size()-1,j=n-1;i>=0&&j>=0;j--,i--){
这个&与&&的区别一个是按位运算,另一个是逻辑运算,&&常用于条件的判断,且是条件运算
https://www.cnblogs.com/kuihuayou/p/7365075.html
https://blog.csdn.net/duyiwuerluozhixiang/article/details/85637918
自己经常爱犯的一个错误:比较相等用两个等号而不是用一个=
151 反转字符串里的单词
给句子里的单词的顺序进行反转
是不是像离散那个思想,把所有的单词都给反转,然后再根据空格反转每一个单词?
果然是这样
但是上来看卡哥的那个解法自己感觉有点难,然后自己拖了好几天,然后去看了力扣官方的解析,然后觉得它上面那个先反转整个字符串,然后再逐个单词去反转,然后去掉句子首尾的空格是最适合的
本题的难点是去掉句子中的多个空格,而且空格的位置不同,去掉空格的方式也不同,
所以这个方法是先扫描字符,如果遇到字符,且不是第一个单词,就这个单词前加一个空格,然后最后全部反转完毕后,删去末尾的空格
但看这个代码,有疑问的是用来加空格的那个变量,是怎么变化的
有个疑问来着,这个直接在原字符串上进行赋值操作会不会导致覆盖?
不会,因为这个最多是相等,每个单词的个数相同,掠过空格,相当于用橡皮擦掉再抄写,不会覆盖,空格每次只加一个,所以更不会覆盖。
class Solution {
public:
string reverseWords(string s) {
//反转整个句子,再反转整个单词
reverse(s.begin(),s.end());
//注意题目要求,给的字符串的空格可能多个。但是,最后输出的结果
//是单词之间只能包含一个空格来隔开,且句子开头和结尾没有空格,
//所以涉及到一个去除多余空格的问题
int num=0;
int n=s.size();
for(int i=0;i<n;i++){
//
if(s[i]!=' '){
if(num!=0)
s[num++]=' ';//这个num 是怎么来一个单词一个单词的移动的,
int end=i;
while(end<n&&s[end]!=' ')s[num++]=s[end++];//这一行说明了num按单词移动,且给字符串赋值
reverse(s.begin()+num-end+i,s.begin()+num);//在字符串中给这个单词翻转
//末尾减去这个单词的长度,即end-i
i =end;//改变i的初始值,寻找下一个单词
}}
s.erase(s.begin()+num,s.end());//删去末尾的空格
return s;
}
};
二刷:给s[num]赋完空格后别忘了num++。
class Solution {
public:
string reverseWords(string s) {
//反转整个句子,再反转整个单词
reverse(s.begin(),s.end());
//注意题目要求,给的字符串的空格可能多个。但是,最后输出的结果
//是单词之间只能包含一个空格来隔开,且句子开头和结尾没有空格,
//所以涉及到一个去除多余空格的问题
int num=0;
int n=s.size();
for(int i=0;i<n;i++){
if(s[i]!=' '){if(num!=0)s[num++]=' ';
int end=i;
while(end<n&&s[end]!=' '){s[num++]=s[end++];}
//要保证这个end不能是字符串的末尾,也不能超过本单词的长度(用空格来界定)
//赋完值了之后就
reverse(s.begin()+i+num-end,s.begin()+num);
i=end;
}
}
s.erase(s.begin()+num,s.end());
return s;
}
};
卡哥的思路下回再学吧
所以解题思路如下:
移除多余空格
将整个字符串反转
将每个单词反转
举个例子,源字符串为:"the sky is blue "
移除多余空格 : "the sky is blue"
字符串反转:"eulb si yks eht"
单词反转:"blue is sky the"
卡哥在这提高了难度
不要使用辅助空间,空间复杂度要求为O(1)。
不让用split库函数,实际上我也不知道这个函数是干啥的
根据分隔符,把一个字符串分成若干个字串的
string = "Hello, world! This is a sample string."
words = string.split() # 以空格为分隔符分割字符串
print(words) # 输出: ['Hello,', 'world!', 'This', 'is', 'a', 'sample', 'string.']
string = "apple,banana,orange,mango"
fruits = string.split(',') # 以逗号为分隔符分割字符串
print(fruits) # 输出: ['apple', 'banana', 'orange', 'mango']
这个题还有点心窄,给你的字符串的空格会存在多个而且要求你输出前后不允许存在字符串,中间单词与单词直接只能有一个字符串。
这里面复杂的点就是给多余的空格删掉
先来学学erase ,这个函数可以删除指定位置上的字符串,,你先给出位置,后面那个参数给出个数。从某个位置开始删除连续个字符
1.删除单个字符:
std::string str = "Hello, World!";
str.erase(7); // 删除索引为7的字符,结果为"Hello Wrld!"
2.删除指定范围内的字符串
std::string str = "Hello, World!";
str.erase(7, 2); // 从索引7开始删除2个字符,结果为"Hello, Wrld!"
删除特定字符串,你先找到你要删除的字符串的起始位置,可以用find函数来返回。
std::string str = "Hello, World!";
size_t found = str.find("World");
if (found != std::string::npos) {
str.erase(found, 5); // 从找到的位置开始删除5个字符,结果为"Hello, !"
}
erase的时间复杂度为O(n),在for循环里就是O(n^2)的时间复杂度了
所以这里不建议用erase
但是可以先看看如何去掉字符串的收尾和中间多余的空格
剑指Offer58-II.左旋转字符串
以空间换时间
class Solution {
public:
string reverseLeftWords(string s, int n) {
//申请额外的空间
string list=s;
int j=0;
for(int i=n;i<s.size();i++){
list[j++]=s[i];
}
for(int i=0;i<n;i++){
list[j++]=s[i];
}
return list;
}
};
我要是这么声明就不对,因为我声明的是一个字符数组不是字符对象
string list[s.size()];
这样就得要求函数的返回类型是
如果你想从这个函数中返回一个字符串数组,
你可以将函数的返回类型更改为std::string[]或std::vector<std::string>,
这两种类型都可以容纳多个字符串对象。
力扣报的错
no viable conversion from returned value of type 'std::string [s.size()]' to function return type 'std::string'
而string的 返回类型要求返回一个字符对象
一个单独的字符对象必须用函数内已经存在的字符串对象来构成,可以用operator拼接组成新的字符串对象,但是前提必须是用已经存在的字符串对象构成的,这好像涉及到了面向对象的问题
所以必须这么写
string list=s;
可以直接用reverse,然后结合那个离散可逆的原理
先用自带的reverse函数。
reverse需要一个初始位置,和末尾位置,是迭代器
class Solution {
public:
string reverseLeftWords(string s, int n) {
//先给整个字符串来个逆置
reverse(s.begin(),s.end());
//然后单个逆置
reverse(s.begin(),s.end()-n);
reverse(s.end()-n,s.end());
return s;
}
};
然后自己写这个reverse函数
注意字符串一定要引用传递!!!
要不然不改变
reverse 里用的是要进行逆置的数组和要逆置的初始位置和末尾位置。
class Solution {
public: void reverse1(string &s,int m,int n){
for(int i=m,j=0;i<=(m+n)/2;i++,j++){
int temp=s[i];
s[i]=s[n-j];
s[n-j]=temp;
}}
string reverseLeftWords(string s, int n) {
//先给整个字符串来个逆置
reverse1(s,0,s.size()-1);
reverse1(s,0,s.size()-n-1);
reverse1(s,s.size()-n,s.size()-1);
return s;
}
};