avatar

Junyuan Lu(陆俊元)

MPhil student at ZJU Intelligent Autonomous Systems Lab

代码随想录算法训练营第九天 | 151. 翻转字符串里的单词、卡码网:55.右旋转字符串、28. 实现 strStr()、459. 重复的子字符串


151. 翻转字符串里的单词

  • 题目链接:click here

  • 文章讲解:click here

  • 题目描述:给定一个字符串,逐个翻转字符串中的每个单词。

    示例 1:
    输入: “the sky is blue”
    输出: “blue is sky the”

    示例 2:
    输入: “ hello world! “
    输出: “world! hello”
    解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

    示例 3:
    输入: “a good example”
    输出: “example good a”
    解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

  • 思路:
    关于单词的反转,我的思路基本上和讲解的一致,先整体反转,再逐个单词反转。

    关于去除空格,我所实现的版本比讲解要复杂一些,我想的是,只要写过单词,就可以接受下一次写空格,反之则忽略碰到的空格,最后遍历完再检查一下最后一次写的是空格还是字母,对应调整最终长度,再resize字符串到该长度。

    讲解的思路是:只处理非空格元素,并且只有写过单词,才会在写下一个单词前先额外写一格空格。

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class Solution
    {
    public:
    string reverseWords(string s)
    {
    // 先整体反转
    int size = s.size();
    int left = 0;
    int right = size - 1;
    while (left < right)
    {
    swap(s[left++], s[right--]);
    }

    // 再去除空格
    bool write_space = false;
    left = 0;
    right = 0;
    for (; right < size; right++)
    {
    if (s[right] == ' ')
    {
    if (write_space)
    {
    s[left++] = s[right];
    write_space = false;
    }
    }
    else
    {
    s[left++] = s[right];
    write_space = true;
    }
    }
    if (s[left - 1] == ' ')
    {
    left -= 1;
    }
    s.resize(left);

    // 再逐个单词局部反转
    size = s.size();
    left = 0;
    right = 0;
    while (right < size)
    {
    while (right < size && s[right] != ' ')
    {
    right++;
    }

    int temp_left = left;
    int temp_right = right - 1;
    while (temp_left < temp_right)
    {
    swap(s[temp_left++], s[temp_right--]);
    }

    left = ++right;
    }
    return s;
    }
    };
  • 讲解代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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]);
    }
    }

    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;
    }
    };
  • 时空复杂度分析:
    遍历三次string :O(n) 时间复杂度
    无额外存储:O(1) 空间复杂度


卡码网:55.右旋转字符串

  • 题目链接:click here

  • 文章讲解:click here

  • 题目描述:字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。

    例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。

    输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。

    输出:输出共一行,为进行了右旋转操作后的字符串。

  • 思路:
    首先想到了,先把非右旋的部分在string尾部抄一遍,然后再另起一个string,从右旋的部分开始向后摘抄string,即可得到所要的结果,但是显然,空间复杂度不是最好的。

    然后再想就受到了上面题的启发,似乎也可以通过两次反转就实现了右旋,而且还不需要自己找单词的左右边界,题目直接告诉了,更简单。这也和讲解的思路一致。

    此外,左旋其实就是右旋另一部分,本质是一样的。

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    using namespace std;
    int main()
    {
    int k;
    cin >> k;

    string s;
    cin >> s;

    int size = s.size();

    if (k > size)
    {
    cout << s << endl;
    return 0;
    }

    int left = 0;
    int right = size - 1;

    while (left < right)
    {
    swap(s[left++], s[right--]);
    }

    left = 0;
    right = k - 1;

    while (left < right)
    {
    swap(s[left++], s[right--]);
    }

    left = k;
    right = size - 1;

    while (left < right)
    {
    swap(s[left++], s[right--]);
    }

    std::cout << s << std::endl;
    return 0;
    }
  • 时空复杂度分析:
    遍历string:O(n) 时间复杂度
    无需额外存储:O(1) 空间复杂度


今日收获

  • 个人博客更新。若Blog有误或是有其他想交流的,左侧有联系方式。

  • 复习:
    今日核心思想:反转再反转实现string中前后元素块的反转,且保持元素块内部顺序不变。

    翻转字符串里的单词:综合性较高的题,两次反转(整体+局部)的想法很重要 + 如何去除额外的空格是模拟题,模仿人的判断过程即可,但是也是利用了双指针的方法,参考 移除元素
    右旋转字符串:技巧题,自己想出来了两次反转的做法,和讲解一致。左旋就是右旋另一半呗,一个意思。

    实现 strStr()重复的子字符串 两道题可跳过,今天暂且放下,周末看。

  • 耗时:2h