剑指 Offer 每日一题 | 3、替换空格

剑指 Offer 每日一题 | 3、替换空格

一、前言

大家好,本文章属于《剑指 Offer 每日一题》中的系列文章中的第 3 篇。

在该系列文章中我将通过刷题练手的方式来回顾一下数据结构与算法基础,同时也会通过博客的形式来分享自己的刷题历程。如果你刚好也有刷算法题的打算,可以互相鼓励学习。我的算法基础薄弱,希望通过这两三个月内的时间弥补这块的漏洞。本次使用的刷题语言为 Java ,预计后期刷第二遍的时候,会采用 Python 来完成。

二、题目

请实现一个函数,把字符串 s 中的每一个空格替换成 “%20” 。

示例1:

输入:s = "We are happy."
输出:"We%20are%20happy."
复制代码

限制:

0 <= s 的长度 <= 10000

三、解题思路

看到这题,最容易想到的是思路1 。这种方式也非常的高效,但是会花费额外的空间。如果都用 Java 来是是实现的话,其实本题差距也不大,都需要用到Stringbuilder 来暂存数组,造成额外空间的浪费,都需要O(n), 但是思路3 用c ++ 来实现同样的逻辑的话,就可以将空间复杂度降到 O(1) 。

3.1 思路1:遍历判断

这题的逻辑很直接,就遍历判断即可,因为 Java 中 String 是不可变的,所以用 StringBuilder 来替代,它在单线程下是线程不安全的,但是效率高。

算法的逻辑为:如果不为空,则把string 的值复制到 builder 上,如果为空,则把 %20 的值复制到 builder 上,所以这样逻辑简单,但是会额外多出一个 O(n) 的 空间 。

3.1.1 代码实现

Java 代码实现:

 private static String resolve(String s) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            if(s.charAt(i)==' '){
                builder.append("%20");
            }else{
                builder.append(s.charAt(i));
            }
        }
        return builder.toString();
    }
复制代码
3.1.2 执行效果

image-20210715201609845

3.2 思路2:机灵鬼做法

这种做法就是利用 各种成熟语言的自带 APi 方法来解决,了解一下即可,实际工作中真正采用的一般是这种,也就是熟知的 API 调用工程师。当然,真正采用这种的话也就脱离了我们刷算法题的本质了。

3.2.1 代码

Java 代码实现:

   // 方式二:机灵鬼做法
    private static String resolve2(String s) {
        return s.replace(" ","%20");
    }
复制代码
3.2.2 执行效果

image-20210715202734255

也可以看到实际上调用 API 消耗 的内存有时候消耗的空间内存可能会大一些。

3.3 思路3:原地解法

这种解法是 书中推荐的写法,但是这有一定的难度。

1、首先,我们先遍历一次字符串,统计出空格的总数,然后计算出被替换之后的字符串的总长度。

假设替换前长度为n,空格为m那么新的长度为n+2m假设替换前长度为 n, 空格为 m 那么新的长度为 n+2*m

2、从字符串的后面开始复制和替换,首先准备两个指针:p1 和 p2,p1 指向原始原始字符串的最末尾,p2指向字符串的末尾。

3、逐步像左移动 p1,把它指向的字符串复制到 p2所指的位置。

4、当 p1 遇到空格的时候,如下图 b,把 p1向左移动一格,把 p2 之前的三个位置插入 %20,p2向前移动3位,如图 c 所示。

5、继续执行前面的步骤,然后完毕。

img

3.3.1 代码实现

Java 中没有指针的概念,但也不是不可以是实现,下面用这种方式完成。

Java 代码实现:

 // num 是用来记录空格数的
        int num=0;
        for (int i = 0; i < s.length(); i++) {
            if(s.charAt(i)==' '){
                num++;
            }
        }
        // 添加空格
        StringBuilder builder = new StringBuilder(s);
        for (int i = 0; i < num*2; i++) {
            builder.append(" ");
        }
        int p1=s.length()-1;// p1 指针
        int p2=builder.length()-1; // p2 指针
        for (int i = s.length(); i >0 ; i--) {
            if(builder.charAt(p1)==' '){
                builder.setCharAt(p2,'0');
                builder.setCharAt(p2-1,'2');
                builder.setCharAt(p2-2,'%');
                p1--;
                p2=p2-3;
            }else{
                builder.setCharAt(p2,builder.charAt(p1));
                p1--;
                p2--;
            }
        }
        return builder.toString();
复制代码
3.3.2 执行效果

image-20210715212248246

四、小结

本题的思路3 方式比较新颖,一般也没这么想过,发现 Java 确实比较耗性能,字符串不可变。但思路 3 采用 Java 的方式还原了 c++ 中指针对此的操作,也让性能提升了部分。

本题主要对字符串进行了考察,三种思路都需要掌握。

好了,今天的刷题就到这了,欢迎点赞评论交流,剑指 Offer 刷题之旅将继续展开!

分类:
后端
标签: