• 字符串4:反转字符串中的单词


    主要是我自己刷题的一些记录过程。如果有错可以指出哦,大家一起进步。
    转载代码随想录
    原文链接:
    代码随想录
    leetcode链接:344. 反转字符串

    题目:

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

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

    示例:

    示例 1:

    输入:s = "the sky is blue"
    输出:"blue is sky the"
    
    • 1
    • 2

    示例 2:

    输入:s = "  hello world  "
    输出:"world hello"
    解释:反转后的字符串中不能存在前导空格和尾随空格。
    
    • 1
    • 2
    • 3

    示例 3:

    输入:s = "a good   example"
    输出:"example good a"
    解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
    
    • 1
    • 2
    • 3

    提示:

    1 <= s.length <= 104
    s 包含英文大小写字母、数字和空格 ’ ’
    s 中 至少存在一个 单词

    进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。

    思路:

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

    一些同学会使用split库函数,分隔单词,然后定义一个新的string字符串,最后再把单词倒序相加,那么这道题题目就是一道水题了,失去了它的意义。

    所以这里我还是提高一下本题的难度:不要使用辅助空间,空间复杂度要求为O(1)。

    不能使用辅助空间之后,那么只能在原字符串上下功夫了。

    想一下,我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。

    所以解题思路如下:

    移除多余空格
    将整个字符串反转
    将每个单词反转
    
    • 1
    • 2
    • 3
    举个例子,源字符串为:"the sky is blue "
    移除多余空格 : "the sky is blue"
    字符串反转:"eulb si yks eht"
    单词反转:"blue is sky the"
    
    • 1
    • 2
    • 3
    • 4

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

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

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    逻辑很简单,从前向后遍历,遇到空格了就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); // 重新设置字符串大小
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    leetcode上的测试集里,字符串的长度不够长,如果足够长,性能差距会非常明显。
    leetcode的测程序耗时不是很准确的。
    版本一的代码是一般的思考过程,就是 先移除字符串前的空格,再移除中间的,再移除后面部分。

    不过其实还可以优化,这部分和27.移除元素的逻辑是一样一样的,本题是移除空格,而 27.移除元素 就是移除元素。
    所以代码可以写的很精简,大家可以看 如下 代码 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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // 反转字符串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]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    整体代码如下:

    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;
        }
    };
    
    • 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:
    	string reverseWords(string s) {
    		int len = s.length();
    		s.insert(len, " #");
    		int endIndex = len;
    		++len;
    		while (s[0] != '#') {
    			if (s[0] == ' ') {
    				s.erase(0, 1);
    				continue;
    			}
    			int pos = s.find(" ");
    			string substr = " " + s.substr(0, pos);
    			s.erase(0, pos);
    			s.insert(s.find('#')+1, substr);
    		}
    		s.erase(0, 2);
    		return s;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    代理服务器拒绝连接
    [2023年]-hadoop面试真题(三)
    计算机毕业设计之java+springboot基于vue的网吧管理系统
    stride与padding对输出尺寸的计算
    华为路由器MPLS VPN综合实验
    舵机应该如果选择?讲讲模拟舵机,数字舵机和总线舵机的区别
    用户层与驱动层通信
    SpringBoot 自动配置@EnableAutoConfiguration
    人工神经网络原理与实践,人工神经网络实战教程
    世界技能大赛夺冠背后,亚马逊云科技如何培养云计算技能人才?
  • 原文地址:https://blog.csdn.net/Ge_yangwen/article/details/128085631