Leetcode记录:字符串例题

反转字符串

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

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]);
    }
}

反转字符串

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。 如果剩余字符少于 k 个,则将剩余字符全部反转。如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

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;
}

替换数字

给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。

#include <iostream>
using namespace std;
int main() {
    string s;
    while (cin >> s) {
        int sOldIndex = s.size() - 1;
        int count = 0; // 统计数字的个数
        for (int i = 0; i < s.size(); i++) {
            if (s[i] >= '0' && s[i] <= '9') {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是将每个数字替换成"number"之后的大小
        s.resize(s.size() + count * 5);
        int sNewIndex = s.size() - 1;
        // 从后往前将数字替换为"number"
        while (sOldIndex >= 0) {
            if (s[sOldIndex] >= '0' && s[sOldIndex] <= '9') {
                s[sNewIndex--] = 'r';
                s[sNewIndex--] = 'e';
                s[sNewIndex--] = 'b';
                s[sNewIndex--] = 'm';
                s[sNewIndex--] = 'u';
                s[sNewIndex--] = 'n';
            } else {
                s[sNewIndex--] = s[sOldIndex];
            }
            sOldIndex--;
        }
        cout << s << endl;       
    }
}

反转字符串中的字母

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

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。

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

一种最直观的方法是双指针法,从后向前依次寻找每个单词的首和尾:

string reverseWords(string s) {
    // 使用双指针
    int m = s.size() - 1;
    string res;
    // 除去尾部空格
    while (s[m] == ' ' && m > 0) m--;
    int n = m; // n是另一个指针
    while (m >= 0) {
        while (m >= 0 && s[m] != ' ') m--;
        res += s.substr(m + 1, n - m) + " "; // 获取单词并加上空格
        while (m >= 0 && s[m] == ' ') m--;
        n = m;
    }
    return res.substr(0, res.size() - 1); // 忽略最后一位的空格
}

也可以原地进行操作,首先先将字符串翻转,再去从头开始填充每个单词:

string reverseWords(string s) {
    // 反转整个字符串
    reverse(s.begin(), s.end());

    int n = s.size();
    int idx = 0;
    for (int start = 0; start < n; ++start) {
        if (s[start] != ' ') {
            // 填一个空白字符然后将idx移动到下一个单词的开头位置
            if (idx != 0) s[idx++] = ' ';

            // 循环遍历至单词的末尾
            int end = start;
            while (end < n && s[end] != ' ') 
                s[idx++] = s[end++];
            // 此时idx和end都指向单词的末尾的后一个位置
            // 反转整个单词
            reverse(s.begin() + idx - (end - start), s.begin() + idx);

            // 更新start,去找下一个单词
            start = end;
        }
    }
    s.erase(s.begin() + idx, s.end());
    return s;
}

更直观的写法为:

void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }

    void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
        int slow = 0;   //整体思想参考https://programmercarl.com/0027.移除元素.html
        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的大小即为去除多余空格后的大小。
    }

    string reverseWords(string s) {
        removeExtraSpaces(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
        reverse(s, 0, s.size() - 1);
        int start = 0; //removeExtraSpaces后保证第一个单词的开始下标一定是0。
        for (int i = 0; i <= s.size(); ++i) {
            if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
                reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
                start = i + 1; //更新下一个单词的开始下标start
            }
        }
        return s;
    }

还有一种办法是利用双端队列,由于双端队列支持从队列头部插入的方法,因此我们可以沿着字符串一个一个单词处理,然后将单词压入队列的头部,再将队列转成字符串即可。

这里用的是std::deque,是一个双端队列,支持push_back,push_front,pop_back,pop_front.

queue是FIFO,stack是LIFO,它们只支持push,pop.

string reverseWords(string s) {
    int left = 0, right = s.size() - 1;
    // 去掉字符串开头的空白字符
    while (left <= right && s[left] == ' ') ++left;

    // 去掉字符串末尾的空白字符
    while (left <= right && s[right] == ' ') --right;

    deque<string> d;
    string word;

    while (left <= right) {
        char c = s[left];
        if (word.size() && c == ' ') {
            // 将单词 push 到队列的头部
            d.push_front(move(word));
            word = "";
        }
        else if (c != ' ') {
            word += c;
        }
        ++left;
    }
    d.push_front(move(word));
    
    string ans;
    while (!d.empty()) {
        ans += d.front();
        d.pop_front();
        if (!d.empty()) ans += ' ';
    }
    return ans;
}

右旋字符串

字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。要求不额外使用空间。

进行三次翻转即可:

#include<iostream>
#include<algorithm>
using namespace std;
int main() {
    int n;
    string s;
    cin >> n;
    cin >> s;
    int len = s.size(); //获取长度

    reverse(s.begin(), s.end()); // 整体反转
    reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n
    reverse(s.begin() + n, s.end()); // 再反转后一段
    cout << s << endl;
} 

左旋转和右旋转的思路是一样的。