当前位置: 首页 > article >正文

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

题目

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

示例 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://www.kler.cn/a/274677.html

相关文章:

  • ASP.NET|日常开发中数据集合详解
  • JVM简介—1.Java内存区域
  • Linux网络功能 - 服务和客户端程序CS架构和简单web服务示例
  • 单片机上电后程序不运行怎么排查问题?
  • halcon单相机+机器人*眼在手外标定心得
  • javaFX.(蜜雪冰城点餐小程序)MySQL数据库
  • Unity构建详解(2)——SBP的初始设置和脚本编译
  • 【自记录】VS2022编译OpenSSL1.0.2u
  • 电装DENSO 嵌入式岗笔试
  • Qt + HTTP 线程交互类封装
  • MNN createSession 之创建流水线后端(四)
  • 记录解决问题--activiti8.2 流程图图片由png改为svg前端不显示图片问题
  • word excel ppt转pdf
  • 常见传感器的原理 和 常见滤波算法实现
  • 开源模型应用落地-安全合规篇-模型输出合规性检测(三)
  • Bert的一些理解
  • 同步方法和同步块,哪个是更好的选择?什么是线程同步和线程互斥,有哪几种实现方式?
  • (简单成功)Mac:命令设置别名
  • 原生html vue3使用element plus 的树tree上移下移案例源码
  • 轻松解锁微博视频:基于Perl的下载解决方案
  • java算法题每日多道
  • 行业模板|DataEase制造行业大屏模板推荐
  • Angular进阶之八: Angular Animation在项目中的实践经验
  • 【leetcode热题】二叉搜索树迭代器
  • Rust 中Self 关键字的两种不同用法
  • 微信小程序 canvas层级过高覆盖原生组件