代码随想录阅读笔记-字符串【翻转字符串中单词】

news/2024/7/7 20:42:40

题目

给定一个字符串,逐个翻转字符串中的每个单词。

示例 1:
输入: "the sky is blue"
输出: "blue is sky the"

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

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

思路  

这道题目可以说是综合考察了字符串的多种操作。

大家第一反应会使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加,但是这样的空间复杂度还是没有达到最优,所以如果不要使用辅助空间,空间复杂度要求为O(1),有没有什么解决办法呢?

不能使用辅助空间之后,那么只能在原字符串上下功夫了。想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。

所以解题思路如下:

  • 移除多余空格
  • 将整个字符串反转
  • 将每个单词反转

举个例子,源字符串为:"the sky is blue "

  • 移除多余空格 : "the sky is blue"
  • 字符串反转:"eulb si yks eht"
  • 单词反转:"blue is sky the"

这样我们就完成了翻转字符串里的单词。

思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些人会上来写如下代码:

void removeExtraSpaces(string& s) {
    for (int i = s.size() - 1; i > 0; i--) {
        if (s[i] == s[i - 1] && s[i] == ' ') {
            s.erase(s.begin() + i);
        }
    }
    // 删除字符串最后面的空格
    if (s.size() > 0 && s[s.size() - 1] == ' ') {
        s.erase(s.begin() + s.size() - 1);
    }
    // 删除字符串最前面的空格
    if (s.size() > 0 && s[0] == ' ') {
        s.erase(s.begin());
    }
}

逻辑很简单,从后向前遍历(方便找到删除位置的索引),遇到空格了就erase。

如果不仔细琢磨一下erase的时间复杂度,还以为以上的代码是O(n)的时间复杂度呢。

想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作。

erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。

那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。代码如下:

//版本一 
void removeExtraSpaces(string& s) {
    int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
    // 去掉字符串前面的空格
    while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') {
        fastIndex++;
    }
    for (; fastIndex < s.size(); fastIndex++) {
        // 去掉字符串中间部分的冗余空格
        if (fastIndex - 1 > 0
                && s[fastIndex - 1] == s[fastIndex]
                && s[fastIndex] == ' ') {
            continue;
        } else {
            s[slowIndex++] = s[fastIndex];
        }
    }
    if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格
        s.resize(slowIndex - 1);
    } else {
        s.resize(slowIndex); // 重新设置字符串大小
    }
}

有的人可能发现用erase来移除空格,在leetcode上性能也还行。主要是以下几点;:

  1. leetcode上的测试集里,字符串的长度不够长,如果足够长,性能差距会非常明显。
  2. leetcode的测程序耗时不是很准确的。

版本一的代码是一般的思考过程,就是 先移除字符串前的空格,再移除中间的,再移除后面部分。不过其实还可以优化,这部分和之前的移除元素的逻辑是一样一样的,本题是移除空格,而 移除元素就是移除元素。所以代码可以写的很精简,大家可以看 如下 代码 removeExtraSpaces 函数的实现:

// 版本二 
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的大小即为去除多余空格后的大小。
}

还要实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在反转字符串1和2里已经讲过了。需要注意的是最后一定需要resize一下,不然对之后的处理都会产生比较大的影响。

代码如下:

// 反转字符串s中左闭右闭的区间[start, end]
void reverse(string& s, int start, int end) {
    for (int i = start, j = end; i < j; i++, j--) {
        swap(s[i], s[j]);
    }
}

整体代码如下:

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;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变

http://lihuaxi.xjx100.cn/news/2105703.html

相关文章

Linux学习笔记-Linux学习方法

Linux建议学习路线 计算机概论与硬件相关知识&#xff1a; 因为既然想要走Linux这门路&#xff0c;信息相关的基础技能也不能没有啊&#xff01; 所以先理解一下基础的硬件知识&#xff0c;不用一定要全懂&#xff0c;又不是真的要你去组计算机&#xff0c;但是至少要“听过、有…

Springboot自动校验@NotBlank@NotNull@NotEmpty

1、依赖问题&#xff1a; 查看搭建的SpringBoot项目中 NotEmpty 是否可以引用&#xff0c;查询资料发现从SpringBoot 2.3.0之后放弃了默认对javax.validation 的支持。 <dependency> <groupId>org.springframework.boot</groupId> …

3月第2周精选#ComfyUI爱好者中文社区

社群精华周报&#xff08;3月第2周&#xff09;截止至3.17日 &#xff0c;感谢 WritterGPT ML2627 的记录。 分享者 / 奥特曼 自动将漫画转录为文字并生成剧本 Magi 模型由牛津大学工程科学系的视觉几何组开发&#xff0c;它可以全自动地为漫画页生成剧本&#xff0c;包括谁说了…

KVM安装-kvm彻底卸载-docker安装Webvirtmgr

KVM安装和使用 一、安装 检测硬件是否支持KVM需要硬件的支持,使用命令查看硬件是否支持KVM。如果结果中有vmx(Intel)或svm(AMD)字样,就说明CPU的支持的 egrep ‘(vmx|svm)’ /proc/cpuinfo关闭selinux将 /etc/sysconfig/selinux 中的 SELinux=enforcing 修改为 SELinux=d…

音视频开发之旅——音频基础概念、交叉编译原理和实践(LAME的交叉编译)(iOS)

本文主要讲解的是音频基础概念、交叉编译原理和实践&#xff08;LAME的交叉编译&#xff09;&#xff0c;是基于iOS平台&#xff0c;示例代码如下所示&#xff1a; iOSAudioDemo 另外&#xff0c;Android平台也有相关的文章&#xff0c;如下所示&#xff1a; 音视频开发之旅…

「Linux系列」聊聊vi/vim的3种命令模式

文章目录 一、vim简介二、命令模式1. 光标移动2. 复制、剪切和粘贴3. 撤销和重做4. 搜索和替换5. 显示行号 三、输入模式1. 进入输入模式2. 在输入模式下编辑文本3. 使用特殊字符和快捷键注意事项 四、命令行模式1. 保存和退出2. 查找和替换3. 显示行号和其他设置4. 执行外部命…

Python教程:Python安装目录说明

在 Python 开发中&#xff0c;深入了解 Python 的安装目录结构对于开发者来说是至关重要的。本文以Python 3.8.6为例&#xff0c;详细介绍 Python 的安装目录结构、各个子目录和文件的作用。 1. Python 3.8.6 安装目录结构 Python 3.8.6 的安装目录通常包含以下重要子目录和文件…

代码随想录算法训练营第46天 | 完全背包,139.单词拆分

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 完全背包理论基础&#xff1a; https://programmercarl.com/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98%E7%90%86%E8%AE%BA%E5%9…