LeetCode151反转字符串中的单词

题目描述

  给你一个字符串 s ,请你反转字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
  进阶:如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用 O(1) 额外空间复杂度的 原地 解法。

解析

  这题其实不难想到方法,最简单的就是通过空格分隔来找单词,存储下来后反转就好,查找的方式可以使循环或者类似官方使用正则表达式,但是这样无疑会引入很大的额外空间开销。包括官方的解法二,和解法三,除了解法二的C++方法,其他也都用到了额外的栈或者其他数据结构存储。(解法2的Python用output = []存储新的结果串,Java版本在反转单词的时候创建了新的StringBuilder对象来反转,如果单词长度和原串类似,那么即使除开输出新串的StringBuilder这个不能避免的0(n)空间,也依旧要引入额外的0(n)空间)。
  这里提供一种四指针的方法来逆序,只遍历一遍字符串的同时,使用的额外空间是O(1)。方法是直接修改字符串而不是新建一个字符串后再去拼接,但使用的不是逆序方法。
  首先Java本身字符串不支持可变,但是StringBuilder是可以的,所以创建了一个StringBuilder对象复制s的数值后后面就不再用s了,用StringBuilder对象模拟输入的是可变的数据类型,也就是说,实际提交上去的额外空间依然是O(n),但实际上只要把输入的s的类型由String改成StringBuilder,额外空间就是O(1),因为串s的作用就是创建StringBuilder对象,后面再也没有用到。
  思路就是前后找单词,创建四个指针left1,left2,right1,right2。前面找的同时就把字符往后面加,后面找的同时就把字符往前面拼,完事儿后删除left1/left2和right1right2。主要麻烦的就是在前面新增或者删除字符后需要改对应的指针,不过在算法过程中会将原字符串扩充到原字符串+最长的单词的长度。
在这里插入图片描述

class Solution {
    public String reverseWords(String s) {

        if(s.indexOf(' ') == -1){
            return s;
        }

        StringBuilder res = new StringBuilder(s.trim());

        int left1 = 0;
        int left2 = 0;
        int right1 = res.length() - 1;
        int right2 = right1;

        while(left2 <= right2){
            // 前找单词,放到后面
            int temp = right1;
            while(left2 <= right2 && res.charAt(left2) != ' '){
                res.insert(temp + 1, res.charAt(left2));
                temp ++;
                left2 ++;
            }
            // 这个空格可以直接加,因为第一个一定会执行
            res.insert(temp + 1, ' ');

            // 后面找单词,放到前面,然后改指针
            temp = left1;

            left1 ++;
            left2 ++;
            right1 ++;
            right2 ++;
            
            // 这个空格需要判断后再加,因为不一定会执行
            if (left2 <= right2 && res.charAt(right2) != ' ') {
                res.insert(temp, ' ');
            }
            
            while(left2 <= right2 && res.charAt(right2) != ' '){
                res.insert(temp, res.charAt(right2));
                left1 ++;
                left2 ++;
                right1 ++;
            }

            // 找空格
            while(left2 <= right2 && res.charAt(left2)  == ' '){
                left2 ++;
            }
            while(left2 <= right2 && res.charAt(right2)  == ' '){
                right2 --;
            }

            // 删除左边已经搜索过的
            res.delete(left1, left2);

            // 修改删除后的指针
            int x = left2 - left1;
            left2 -= x;
            right2 -= x;
            right1 -= x;

            // 删除右边已搜索过的
            res.delete(right2 + 1, right1 + 1);

            // 重置指针
            left1 = left2;
            right1 = right2;
        }

        res.deleteCharAt(res.length() - 1);

        return res.toString();
    }
}

  可以看到,效率方面还是比较好的。Java版本额外的O(n)作为输出无法避免。如果能修改输入类型,能够减少O(n)的额外空间,达到O(1)级别的空间开销。
在这里插入图片描述

空间换时间

  这个就挺简单了,从后往前扫描,然后把扫描到的单词放到新的字符数组中即可,由于长度已知就不需要使用StringBuilder了,这样能够节省时间。

public class Solution {
    public String reverseWords(String s) {
        char[] c = s.toCharArray();
        char[] result = new char[s.length()];
        int fast = s.length() - 1;
        int index = 0;

        while (fast >= 0) {
            // 跳过末尾空格
            while (fast >= 0 && c[fast] == ' ') fast--;
            if (fast < 0) break;  // 如果全部是空格,则直接退出
            int slow = fast;
            // 定位单词的起始位置
            while (fast >= 0 && c[fast] != ' ') fast--;
            // 复制单词到result中
            if (index > 0) result[index++] = ' ';  // 在添加单词前,如果不是第一个单词,先添加空格
            for (int i = fast + 1; i <= slow; i++) {
                result[index++] = c[i];
            }
        }
        // 返回时不包含末尾的空格
        return new String(result, 0, index);
    }
}

在这里插入图片描述

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值