代码随想录 训练营 | Day 8 - 344.反转字符串、541. 反转字符串II、剑指Offer 05.替换空格、151.翻转字符串里的单词、剑指Offer58-II.左旋转字符串

发布于:2022-12-04 ⋅ 阅读:(273) ⋅ 点赞:(0)

        这5道题相对简单,其中第3题(剑指Offer 05.替换空格)自己做时没有原地操作,第4题(151.翻转字符串里的单词)和第5题(剑指Offer58-II.左旋转字符串)对字符串反转的运用比较深入。再难以一位一位原地操作达到目的的时候,可以考虑对字符串的反转操作。从这几道题中也熟悉了swap()、reverse()、erase()、resize()等操作。


         第1题(344.反转字符串)很简单,循环种左右指针所指字符交换,再向中间靠拢即可。

class Solution {
public:
    void reverseString(vector<char>& s) {
        int l = 0, r = s.size() - 1;
        while (l < r) {
            swap(s[l++], s[r--]);
        }
        return;
    }
};

代码中主要再次熟悉了swap的使用,从题解中学习到swap()的实现除了利用额外的临时变量tmp外,还有本地异或的方法如下:

a ^= b;
b ^= a;
a ^= b;

因为0 ^ x(或x ^ 0)等于x,1 ^ x(或x ^ 1)等于!x,所以一个数连续与0异或2次,或与1异或2次,结果都等于这个数本身。进而得到,a与b连续异或2次,结果还是a;反之亦然。所以首先令a = a ^ b,再令b = b ^ (a ^ b) = a,最后a = (a ^ b) ^ a = b。


        第2题(541. 反转字符串II)也相对较容易,但还是第3次才AC。主要原因在于没认真看题,没看到段长度在[k, 2k)之间和(0, k)时也要进行相应反转。方法是指定左右指针作为反转的范围,每次循环指针都加2k,但具体要反转时右指针需取原右指针和数组长度中的较小值以满足在(0, k)时也进行反转的要求。另外,外层循环的条件设置为左指针在倒数第1个字符的左边,以满足段长度在[k, 2k)之间也进行反转的要求。

class Solution {
public:
    string reverseStr(string s, int k) {
        int l = 0, r = k - 1;
        while (l < s.length() - 1) {
            int ll = l, rr = min((int)s.length() - 1, r);
            while (ll < rr) {
                swap(s[ll++], s[rr--]);
            }
            l += 2 * k;
            r += 2 * k;
        }
        return s;
    }
};

         实现过程中曾因为第6行min()里面的两参数数据类型不同而报错,需要把s.length()转为int型。这里要再次注意s.length()返回long类型,与int直接做运算或比较很容易出错。

        题解使用了reverse()函数,代码简洁很多,再结合自己的额min()修改如下:

class Solution {
public:
    string reverseStr(string s, int k) {
        for (int i = 0; i < s.length() - 1; i += 2 * k) {
            reverse(  s.begin() + i, min( s.begin() + i + k, s.end() )  );
        }
        return s;
    }
};

从题解中再次熟悉reverse()的用法,与sort()类似,参数分别为待反转的开始位置和和结束位置。


        第3题(剑指Offer 05.替换空格)也很简单,创建新字符串为空,遍历s的同时补全新字符串即可。

class Solution {
public:
    string replaceSpace(string s) {
        string ans = "";
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == ' ') {
                ans += "%20";
            }
            else {
                ans += s[i];
            }
        }
        return ans;
    }
};

代码中需要注意第6行对空格进行判断时应该用单引号而非双引号,否则会因为双引号表示字符串而报错。

        自己的这个代码使用了额外的数组,空间占用较大,题解是在原数组上做处理。首先计算好新数组的长度后,再扩展旧数组长度,用两个指针从旧数组末尾和新数组末尾从后往前遍历,填充得到新数组。

class Solution {
public:
    string replaceSpace(string s) {
        int cnt0 = 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == ' ') {
                cnt0++;
            }
        }
        s.resize(s.size() + 2 * cnt0);
        for (int i = s.size() - 1, j = s.size() - 2 * cnt0 - 1; i > j; --i, --j) {
            if (s[j] == ' ') {
                s[i--] = '0';
                s[i--] = '2';
                s[i] = '%';
            }
            else {
                s[i] = s[j];
            }
        }
        return s;
    }
};

需要注意第2个循环的条件可以设置为i > j,因为当i追上j时,表示此时剩余的字符串部分已经没有空格了,可以减少不必要的循环。另外第15行i不能再进行减一操作,因为循环本身就会对i和j减一,否则会多减。


        第4题(151.翻转字符串里的单词)比较困难,难在要进行原地操作,非原地操作的解法比较容易。刚开始没仔细看题以为是要在每一个单词内部反转,用代表单词起始、结束位置的双指针reverse()写完后,发现题目要求的是反转句子的单词顺序,单词内部不变。但歪打正着,想到可以先对整个字符串按字符反转,再进行了上面的操作,就能得到想要的结果。于是如此尝试,又失败了,原来题目还有要求删掉多余的空格(包括句子前,单词间,句子末尾的),又又一次说明认真看题的重要性。于是分为4步:

  1. 用双指针法去掉多余空格(之后需要resize());

  2. 按字符反转整个字符串;

  3. 按字符反转每个单词(除最后一个单词);

  4. 按字符反转最后一个单词。

class Solution {
public:
    string reverseWords(string s) {
        // 双指针去掉多余空格
        int l = 0, r = 0;
        while (r < s.size()) {
            bool space = false;
            while (r < s.size() && s[r] == ' ') {
                space = true;
                ++r;
            }
            if (space && l != 0 && r < s.size()) { // l和r分别限定不在开始处或结束处,只在中间添加空格
                s[l++] = ' ';
            }
            if (r < s.size()) { // 避免越界
                s[l++] = s[r++];
            }
        }
        s.resize(l);
        // 反转整个s
        reverse(s.begin(), s.end());
        // 反转每个单词(除最后一个)
        int begin = 0, end = 0;
        while (end < s.size()) {
            if (s[end] == ' ') {
                reverse(s.begin() + begin, s.begin() + end);
                end++;
                begin = end;
            }
            else {
                ++end;
            }
        }
        // 反转最后一个单词
        reverse(s.begin() + begin, s.end());
        return s;
    }
};

其中第15~17行要注意增加避免用于避免越界的if()判断,不能直接将r指向的值赋给l所指位置,最后就是因为这个原因排查了很久才AC。

        题解的思路与自己的基本一致,只有去除多余空格部分的双指针具体实现不太一样(用了类似27.移除元素的方法),且按字符反转每个单词的代码更简洁:

class Solution {
public:
    string reverseWords(string s) {
        // 双指针去掉多余空格
        int slow = 0, fast = 0;
        while (fast < s.size()) {
            if (s[fast] != ' ') {
                if (slow > 0) {
                    s[slow++] = ' ';
                }
                while (fast < s.size() && s[fast] != ' ') {
                    s[slow++] = s[fast++];
                }
            }
            fast++;
        }
        s.resize(slow);
        // 反转整个s
        reverse(s.begin(), s.end());
        // 反转每个单词(除最后一个)
        int begin = 0;
        for (int i = 0; i < s.size(); ++i) {
            if (s[i] == ' ') {
                reverse(s.begin() + begin, s.begin() + i);
                begin = i + 1;
            }
        }
        // 反转最后一个单词
        reverse(s.begin() + begin, s.end());
        return s;
    }
};

需要注意第15行要对fast加1以推动循环前进。另外题解也提到不能用string.erase()在循环内部删除空格(erase()用法可以是erase(pos, n)或erase(first, last),first和last为string类型的迭代器。


        第5题(剑指Offer58-II.左旋转字符串)要一位一位地实现原地操作似乎不容易,但有了第4题(151.翻转字符串里的单词)的经验之后就想到或许可以尝试多次reverse()。先对整个字符串按字符reverse(),再分别对后k个字符和其余的前面字符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;
    }
};
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到