【leetcode_C++_字符串_day7】344_反转字符串&541_反转字符串II&&剑指Offer_05_替换空格&&151_翻转字符串里的单词&&剑指Offer58-II_左旋转字符串.md

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

思路:

比较简单,没啥好说的

class Solution {
public:
    void reverseString(vector<char>& s) {
        char tmp[1] ;

        for(int i=0;i<(s.size())/2;i++)
        {
            tmp[0]=s[i];
            s[i]=s[s.size()-i-1];
            s[s.size()-i-1]=tmp[0];
        }
    }
};

541.反转字符串 II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

输入:s = "abcdefg", k = 2
输出:"bacdfeg"

示例 2:

输入:s = "abcd", k = 2
输出:"bacd"

思路:

一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。

正是在下👇

class Solution {
public:

    string reverseStr(string s, int k) {
        int size=s.size();
        cout<<"size:"<<size<<endl;
        int rest;
        int cnt=0;
        
        int i,j,tmp;
        //每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符
        tmp=size/(2*k);
        cout<<"tmp:"<<tmp<<endl;
        while(cnt<tmp)
        {
            cout<<"2k部分"<<endl;
            for(i=0+cnt*2*k,j=cnt*2*k+(k-1);i<j;i++,j--)
            {
                cout<<"i:"<<i<<"j:"<<j<<endl;
                swap(s[i],s[j]);
            }
            cnt++;
        }
        //判断剩下字符
        rest=size%(2*k);
        cout<<"rest:"<<rest<<endl;
        //如果剩余字符少于 `k` 个,则将剩余字符全部反转。
        if(rest>0&&rest<k)
        {
            cout<<"case1";
            for(i=2*k*tmp,j=size-1;i<j;i++,j--)
            {

                swap(s[i],s[j]);
            }
        }
        else if(rest<2*k)
        {
            //如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
            //[k,2k)

            cout<<"case2";
            for(i=2*k*tmp,j=2*k*tmp+k-1;i<j;i++,j--)
            {

                swap(s[i],s[j]);
            }
            
        }
        return s;
    }
};

这样做可以提交通过,但是用时24ms.耗时长,不经济。

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

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

参考代码如下:

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)) {
            // 1. 每隔 2k 个字符的前 k 个字符进行反转
            // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
            if (i + k <= s.size()) {
                reverse(s, i, i + k - 1);
                continue;
            }
            // 3. 剩余字符少于 k 个,则将剩余字符全部反转。
            reverse(s, i, s.size() - 1);
        }
        return s;
    }
};

剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

思路:

如果想把这道题目做到极致,就不要只用额外的辅助空间了!

首先扩充数组到每个空格替换成"%20"之后的大小。

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

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

替换空格

有同学问了,为什么要从后向前填充,从前向后填充不行么?

从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。

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

这么做有两个好处:

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

心得:

下次遇到数组扩容填充的时候可以考虑双指针法,从后往前操作。

class Solution {
public:
    string replaceSpace(string s) {
        int count=0;//空格的个数
        int i,j;
        int Oldsize=s.size();
        cout<<Oldsize<<endl;

        for(i=0;i<s.size();i++)
        {
            if(s[i]==' ')
                count++;
        }
        // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        cout<<s.size()<<endl;
        //i指向新长度的末尾,j指向旧长度的末尾。
        for(j=Oldsize-1,i=s.size()-1;j>=0;j--)
        {
            cout<<"s[j]:"<<s[j]<<endl;
            if(s[j]!=' ')
            {
                s[i]=s[j];
                i--;
            }      
            else
            {
                s[i]='0';
                s[i-1]='2';
                s[i-2]='%';
                i=i-3;
            }      
        }
        return s;
    }
};

151. 反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"

示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

思路:

  • 移除多余空格--这一步需要思考。

​ 去除所有空格并在相邻单词之间添加空格, 快慢指针。

​ 注意在相邻单词之间添加空格的时候,第一个单词前面是不用加空格的,除了第一个单词外其他的单词的前面都要加空格。所以需要额外判断是不是一个单词,只有不是第一个单词的单词的前面才需要加空格。

    void removeExtraSpaces(string &s)
    {
        int slow,fast;
        slow=0;
        for(fast=0;fast<s.size();fast++)
        {
            if(s[fast]!=' ')//遇到非空格就处理,即删除所有空格。
            {
                if(slow!=0) s[slow++]=' ';//如果slow不等于0,就说明slow不是第一个单词。除了第一个单词,其他单词的前面都要加上空格
                while(fast<s.size()&&s[fast]!=' ')
                {
                    s[slow++]=s[fast++];
                }
            }
        }
        s.resize(slow);//slow的大小即为去除多余空格后的大小。
    }
  • 将整个字符串反转

    这一步比较简单

        void reverse(string& s, int start, int end) {
            for (int i = start, j = end; i < j; i++, j--) {
                swap(s[i], s[j]);
            }
        }
    
  • 将每个单词反转

​ 也是双指针,当i指向的元素是空格的时候,就说明一个单词已经读完了。这个单词的范围是[start,i-1]。翻转这个范围内的元素。然后start赋值为i+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;
            }
        }
        return s;
    }

image-20221025201025448

整体代码:

class Solution {
public:
    //1.移除多余的空格
    //方法:去除所有空格并在相邻单词之间添加空格
    void removeExtraSpaces(string &s)
    {
        int slow,fast;
        slow=0;
        for(fast=0;fast<s.size();fast++)
        {
            if(s[fast]!=' ')//遇到非空格就处理,即删除所有空格。
            {
                if(slow!=0) s[slow++]=' ';//如果slow不等于0,就说明slow不是第一个单词。除了第一个单词,其他单词的前面都要加上空格
                while(fast<s.size()&&s[fast]!=' ')
                {
                    s[slow++]=s[fast++];
                }
            }
        }
        s.resize(slow);//slow的大小即为去除多余空格后的大小。
    }

    //2.将整个字符串反转
    void reverse(string& s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    //3.将每个单词反转
    string reverseWords(string s) {
        removeExtraSpaces(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;
            }
        }
        return s;
    }
};

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"

示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

思路:

参考上一个题目的思想。整体翻转再局部翻转就可以得到左旋的结果。

还是比较简单的。

class Solution {
public:
    void reverse(string &s,int start,int end)
    {
        int j,i;
        for(i=start,j=end-1;i<j;i++,j--)
        {
            swap(s[i],s[j]);
        }
    }

    string reverseLeftWords(string s, int n) {
        reverse(s,0,s.size());//翻转全部
        reverse(s,0,(s.size()-n));//翻转前(size-n)个
        reverse(s,(s.size()-n),s.size());//翻转后n个
        return s;
    }
};

总结:

此时我们已经反转好多次字符串了,来一起回顾一下吧。

在这篇文章344.反转字符串 (opens new window),第一次讲到反转一个字符串应该怎么做,使用了双指针法。

然后发现541. 反转字符串II (opens new window),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

后来在151.翻转字符串里的单词 (opens new window)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。

最后再讲到本题,本题则是先局部反转再 整体反转,与151.翻转字符串里的单词 (opens new window)类似,但是也是一种新的思路。

好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。

posted @ 2022-10-26 16:00  只想毕业的菜狗  阅读(40)  评论(0)    收藏  举报