C++ day8 反转字符串 反转字符串Ⅱ 替换空格 反转字符串里的单词 左旋转字符串

题目1:344 反转字符串

题目链接:反转字符串

对题目的理解

将给定的字符串反转 空间是O(1)

自己的思考

遍历字符串然后存放到新的数组里,但是不满足空间O(1)

想到反转链表使用双指针的思想,所以使用双指针法反转字符串,字符串也是一种数组,所以元素在内存中是连续分布,定义两个指针i,j,一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

使用swap交换两个指针处的值。

class Solution {
public:
    void reverseString(vector<char>& s) {
        for(int i=0, j=s.size()-1;i<s.size()/2;i++,j--){
            swap(s[i],s[j]);
        }
        //这个是void类型,没有返回值
    }
};

swap有两种实现方式

1.数值交换

int tmp = s[i];
s[i] = s[j];
s[j] = tmp;

2.位运算

s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];

题目2:541 反转字符串Ⅱ

题目链接:反转字符串Ⅱ

对题目的理解

i)根据整数k,从字符串开头计数,每次计数到2k,就反转2k中的前k个字符;

ii)剩余字符少于k个,则剩余字符全部反转;

iii)剩余字符大于等于k,小于2k,则只反转前k个字符

自己的思考

首先应用字符串的长度对2k取余, 先处理剩下的字符串,使用for循环+swap函数反转;然后用字符串的长度除以2k,看有多少个2k,然后再对每一个字符串内的数据进行for循环+swap函数,很冗余,不推荐。

使用reverse库函数(左闭右开)

在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。

因为要找的也就是每2 * k 区间的起点,这样写,程序会高效很多。

所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
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);//s[i+k]有值不为空
           else  reverse(s.begin()+i, s.end());
       }
       return s;
    }
};

之所以是i+k<=s.size(),为什么有等号,可以举一个典型的例子:
abc字符串 k=3  当i=0时,i+k=3,而s.size()=3,因此可以取等号

自己编写反转程序reverse函数的代码(左闭右闭)

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)或O(n), 取决于使用的语言中字符串是否可以修改.
class Solution {
public:
    void reverse(string& s, int start, int end){
        for(int i=start,j=end;i<j;i++,j--)
        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()){
           reverse(s, i, i+k-1);//s[i+k]有值不为空
           continue;}
           reverse(s, i, s.size()-1);
       }
       return s;
    }
};

另一种思路解法

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
class Solution {
public:
    string reverseStr(string s, int k) {
       int n = s.size(),pos = 0;
       while(pos<n){
           if(pos+k<n){
               reverse(s.begin()+pos, s.begin()+pos+k);
           }
           else reverse(s.begin()+pos,s.end());
           pos += 2*k;
       }
       return s;
    }
};

但是这个程序里面有一个疑问:???就是这段代码为什么会报错???

注意分情况要画{}呀,是因为没有打印{},所以continue会直接拦截住  reverse(s.begin()+i, s.end()),使这一句永远都不会执行。

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);//s[i+k]有值不为空
           continue;}
           reverse(s.begin()+i, s.end());
       }
       return s;
    }
};这段代码为什么会计算出错

题目3:05替换空格

题目链接:替换空格(链接是力扣的)


对题目的理解

(力扣:将'.'替换为空格)  将空格换成%20  注意%20其实是3个字符:%   2    0

自己的思考

还是遍历的思想,遇到空格,就替换为20%,不行,一个位置只能换一个位置,但是20%有两个位置,无法换

双指针法

首先扩充数组到每个空格替换成"%20"之后的大小。先数有多少个空格,有多少个就在后面补上它的2倍,因为有3个字符嘛,相比原来,多了两个个字符;

然后从后向前替换空格,也就是双指针法,过程如下:

i指向新长度的末尾,j指向旧长度的末尾。

当j指针指向不为空格的元素时,将该元素后移到i指针指向的位置,然后i指针和j指针同时向前移一位再判断j指针指向的位置处是否为空格,若不是空格,则继续讲该元素移到i指针指向的位置,若是j指针指向的位置是空格,则i指针处填冲字符0,然后i指针向前移动一位,填充字符2,i指针再向前移动一位,填充%,然后j指针再移动,继续判断该位置处的字符是否为空格.

很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

这样做有许多好处:

  1. 不用申请新数组。
  2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。

剑指offer题目(20%)

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
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;
    }
};

力扣题目(.换成空格)

//力扣题目代码
class Solution {
public:
    string pathEncryption(string path) {
        int count = 0;//统计.的个数
        int size = path.size();
        for(int i=0;i<path.size();i++){
            if(path[i]=='.'){
                path[i] = ' ';//计数.的个数
            }
        }
    return path;
    }
};

题目4:151翻转字符串里的单词

题目链接:翻转字符串里的单词

对题目的理解

反转字符串中单词(没有空格)的顺序,s中使用大于等于1个空格将单词分开,返回的单词顺序颠倒且仅在单词之间用单个空格分隔。

自己的思考

以空格结束作为一个单词的标志,或者是判断一个单词,判断这个单词两端都要空格,采用双指针

双指针法

本题不使用辅助空间,空间复杂度为O(1)

解题思路

i)移除多余空格

ii)将整个字符串反转

iii)将每个单词反转

所以,将这段程序进行拆解,分解成两个任务,去空格和翻转

翻转的程序好写


    void reverse(string& s,int start,int end){
        for(int i=start,j=end;i<j;i++,j--){
            swap(s[i],s[j]);
        }//定义的是一个左闭右闭区间,因为第三个参数是end
    }

难点是去空格的程序,因为是有选择的去空格,该段程序有两个解法

①分类讨论去空格

void removeExtraSpaces(string& s){
        int slow = 0, fast = 0;
        //去掉字符串前面的空格
        while(s.size()>0 && fast<s.size() && s[fast] == ' ' ){
            fast++;
        }//此时fast最后指向第一个不为空格的元素,即第一个单词的第一个英文字母
        for(; fast<s.size();fast++){
            //去掉字符串中间部分的冗余空格
            if(fast-1>0 && s[fast-1]==s[fast] && s[fast]==' '){
                continue;
            }//知道fast的紧挨着的两个元素不是空格时为止
            else{
                s[slow] = s[fast];
                slow++;
            }//slow指向不为空格的元素,即英文字母
            }
        if(slow-1>0 && s[slow-1]==' '){//去掉字符串末尾的空格
            s.resize(slow-1);  //resize操作
        }
        else{
            s.resize(slow);//重新设置字符串的大小
        }
        }

②使用双指针法去除空格(这个对我来讲有点难理解)

 void removeExtraSpaces(string& s){//去除所有空格,并在相邻单词之间添加空格,快慢指针
        int slow = 0;
        for(int fast=0;fast<s.size();fast++){
            if(s[fast]!=' '){
                if(slow!=0) s[slow++] = ' ';
                while(fast<s.size() && s[fast]!=' '){
                    s[slow++] = s[fast++];
                }
            }
        }
        s.resize(slow);
    }

完整代码(解法1):

  • 时间复杂度: O(n)
  • 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变
class Solution {
public:
    void removeExtraSpaces(string& s){
        int slow = 0, fast = 0;
        //去掉字符串前面的空格
        while(s.size()>0 && fast<s.size() && s[fast] == ' ' ){
            fast++;
        }//此时fast最后指向第一个不为空格的元素,即第一个单词的第一个英文字母
        for(; fast<s.size();fast++){
            //去掉字符串中间部分的冗余空格
            if(fast-1>0 && s[fast-1]==s[fast] && s[fast]==' '){
                continue;
            }//知道fast的紧挨着的两个元素不是空格时为止
            else{
                s[slow] = s[fast];
                slow++;
            }//slow指向不为空格的元素,即英文字母
            }
        if(slow-1>0 && s[slow-1]==' '){//去掉字符串末尾的空格
            s.resize(slow-1);  //resize操作
        }
        else{
            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]);
        }//定义的是一个左闭右闭区间,因为第三个参数是end
    }

    string reverseWords(string s) {
        removeExtraSpaces(s);//先去除字符串中没有必要的空格
        reverse(s,0,s.size()-1);//将整个字符串颠倒顺序是因为后续进行reverse时,i-1,即对应s.size()-1,这里一定要是小于号,不然会报错
        int start = 0;  //第一个单词的下标一定是0,
        for(int i=0;i<=s.size();i++){//这里是小于等于
            if(i==s.size() || s[i]==' '){ //到达空格或串尾,说明一个单词结束,进行反转
                reverse(s,start,i-1);//注意这是自己编写的reverse函数,是左闭右闭的反转
                start = i+1;//s[i]处是空格,所以start从i+1处开始
            }
        }
    return s;

    }
};

完整代码(解法2):

  • 时间复杂度: O(n)
  • 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变
class Solution {
public:
    void removeExtraSpaces(string& s){//去除所有空格,并在相邻单词之间添加空格,快慢指针
        int slow = 0;
        for(int fast=0;fast<s.size();fast++){
            if(s[fast]!=' '){
                if(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]);
        }//定义的是一个左闭右闭区间,因为第三个参数是end
    }

    string reverseWords(string s) {
        removeExtraSpaces(s);//先去除字符串中没有必要的空格
        reverse(s,0,s.size()-1);//将整个字符串颠倒顺序是因为后续进行reverse时,i-1,即对应s.size()-1,这里一定要是小于号,不然会报错
        int start = 0;  //第一个单词的下标一定是0,
        for(int i=0;i<=s.size();i++){//这里是小于等于
            if(i==s.size() || s[i]==' '){ //到达空格或串尾,说明一个单词结束,进行反转
                reverse(s,start,i-1);//注意这是自己编写的reverse函数,是左闭右闭的反转
                start = i+1;//s[i]处是空格,所以start从i+1处开始
            }
        }
    return s;

    }
};

将代码自己按照逻辑又过了一遍,添加了一些没有的注释。

class Solution {
public:
    //首先去除字符串中所有的空格,定义一个函数
    void removespace(string& s){
        //空格在开头
        int slow = 0, fast = 0;
        while(s.size()>0 && fast<s.size() && s[fast]==' '){
            fast++;//保证开头的空格都删掉,fast此时指向第一个单词的第一个字母
        }
        //空格在中间,若单词之间的空格多于一个,则应该删除这个空格
        for(;fast<s.size();fast++){
            if(fast-1>0 && s[fast]==s[fast-1] && s[fast]==' '){
                continue;
            }
            else s[slow++] = s[fast];
        }
        //空格在结尾
        if(slow-1>0 && s[slow-1]==' '){
            s.resize(slow-1);//这里为啥要是slow-1呢,因为前一段处理中间的数据时,for循环最后进行了slow++
        }
        else 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) {
        //首先去除空格
        removespace(s);
        //翻转
        reverse(s,0,s.size()-1);
        //将每个单词进行翻转
        //每个单词的确定条件是遇到了空格
        int start = 0;
        for(int i=0;i<=s.size();i++){
            if(s[i]== ' ' || i==s.size()){
                reverse(s,start,i-1);//这里start,i-1传递的是位置
                start = i+1;//start代表一个单词的起始位置,一定要在if判断里面,不能在if判断外面
            }
        }
        return s;

    }
};

题目5:58-Ⅱ 左旋转字符串

题目链接:左旋转字符串

对题目的理解

把字符串前面的若干个字符转移到字符串的尾部。

解法

局部反转+整体反转

具体步骤为:

  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串
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;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值