吃透一道题1.0:Java算法中的字符串压缩

写在开头:对于一道算法题,或许在刚开始接触之时我们无法敲出一份“独家”的代码解答,但我们可以先从阅读——理解别人提供的解答中学习他们的思维过程和逻辑,一点一点汲取——转化为独属于自己的代码灵感。

这是第一篇关于算法的内容,所以更想从一个新手小白的视角来观看算法的题解并模拟我的思考过程,为大家解决在阅读题解中可能会遇到的疑惑、困难并分享我的一些灵感思路。

或许你绞尽脑汁想不明白的地方,可以在我这里找到答案。

写一道代码算法题,其实就像在高中的时候做一道数学大题,只不过可能是一道比较难的数学大题——像往往位于试卷靠后的解析几何、导数等等。

而能否做出来这道题,就像我以前数学老师说的,取决于你的思维是否够“通透”,这是一个很有智慧的词,我觉得同样适用于算法题里。

后期我也会出一篇个人对“通透”这个词的理解,从数学视角结合我的java相关知识做一次mix+的融合分享。

目录

一、从审题出发

二、于他人解答中深挖

三、为代码划分框架

循环1

循环2

判断1

判断2

判断3

四、解惑

1.理解代码的含义

1)定义count的用途

2)几个新出现的Java方法

2.理解代码中的内在逻辑(重点)

1)对语句i=j-1的一些分析

2)关于int i = j的位置问题

五、拓展延伸

思考:for循环中()中的i++在什么时候执行?


一、审题

先看题如下:
实现一个算法来压缩一个字符串。压缩的要求如下:
1.需要判断压缩能不能节省空间,仅在压缩后字符串比原字符串长度更短时进行压缩
2.压缩的格式是将连续相同字符替换为字符 +数字形式,例如“AAABCCDDDD”变为“A3BC2D4"


二、解答

先浏览一遍他人的解答,在心中不断思考,如果有困惑不理解的地方可以先标注一下

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String str=scan.next();
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<str.length();i++){
          int count=0;
          int j=i;
          for(;j<str.length();j++){
            if(str.charAt(i)==str.charAt(j)){
              count++;
            }
            else{
              break;
            }
          }
          if(count>1){
            sb.append(str.charAt(i));
            sb.append(count);
            i=j-1;
          }
          else{
            sb.append(str.charAt(i));
          }
        }
        if(sb.length()<str.length()){
          System.out.print(sb.toString());
        }
        else{
          System.out.print("NO");
        }
        scan.close();
    }
}

三、为代码划分框架

在美术中有“画人先画骨”的说法,在风水中也有“望气术”来看到宅体的气场观察吉凶,这些都说明对把握事物大体的能力非常重要,那我们在敲代码的时候也要带着这种“整体”意识去写。

在代码写完后,可以为其划分大体框架,框架的清晰程度也能反映一个程序员在敲代码时思路是否严谨顺畅。

这一模块的意义在于方便大家清楚直观的看出题解的代码模块组成,方便后面的理解内在逻辑。

然后先大致描述一下这几个循环判断都是用来干嘛的,具体细节可以在4.2中进行查看。

1)循环1

主要用于循环遍历,为外部循环,假设字符串为“aaabbb”,则索引为0-5,索引的概念会在判断1中的charAt方法中用到。

其中循环刚开始会从i=0开始向下执行,在这里我们不妨停留一两秒,有没有想过for循环()中的语句(即为初始化语句、循环条件和更新语句)每一个分别的执行时间呢?是同时执行吗?

2)循环2

这段为内部循环内部循环的作用是为判断是否有重复的字符提供判断条件,为同一字符不同索引段落之间进行比较字符是否相同。

3)判断1

判断内部循环与外部循环需要比较的字母是否相同,注意回顾charAt()方法的具体用法在四中会有提及。

4)判断2

用来判断是否有相同重复的字符,如果有则将多个相同的字符压缩后添加至StringBuilder上,在这个判断里有一个重难点“i=j-1”存在的意义是什么,这是我们需要研究的。

在else中如果没有重复的字符则直接将其单个字符char原封不动地加到StringBuilder上。

5)判断3

判断压缩后的字符串长度是否小于原字符串,如果满足条件则输出压缩后的字符串反之则输出no。

这边需要注意xxx.length()这边是有括号的不要忘记加上去了。

四、解惑

1.理解代码的含义

1)定义count的用途

众所周知,count在Java中一般用作计数器,那在这边呢,count用作计算相同字符出现的次数,继续拿“aaabbb”举例,循环1中首先i=0,然后开始进入内部代码(j=0),结果就是a0和a0比较发现a=a,计数器加一,循环2继续;然后a0和a1比较发现a=a,计数器在加一,循环2继续;a0和a2比较,同上,但发现a0和b4比较后发现不等(实则为str.charAt(i=0)和str.char(j=3)的比较),此时循环2终止跳出进入下一流程。

2)几个新出现的Java方法

方法名具体用途
StringBuilder

在这里可以与String进行比较记忆。比如“aaabbb”我们想要的压缩结果是“a3b4”,由于字符串一旦创建就不能再修改,所以我们不可能在String的基础上进行修改,这时候StringBuilder作为一个载体出现了(像一个中介),它具有动态性的特点,我们可以对它做一些删除、添加等操作。

charAt();

charAt() 是 Java 中 String 类的一个方法,用于获取指定索引位置的字符

例如,在代码`s1.charAt(i)`中,`charAt(i)` 表示获取字符串`s1`中索引为`i`的字符。如果`s1`是字符串"hello",那么`s1.charAt(0)`返回的结果就是字符'h',`s1.charAt(1)`返回的结果是字符'e',依此类推。

append();

用于向固定对象的末尾追加指定的对象,可以理解为Java中特定的加号,像一个裁缝一样,也有点像物理电学中的串联电路,这样方便记忆理解。

.toString();

将给定的对象转换为字符串的形式输出,直观明了。

举例
   StringBuilder sb = new StringBuilder();
   sb.append("Hello");
   sb.append(" ");
   sb.append("World");
   String result = sb.toString(); // "Hello World"

注:这里没有要骂人的意思!!

StringBuilder sb = new StringBuilder();

        // 添加字符串
        sb.append("Hello, ");
        sb.append("world!");

 System.out.println(sb.toString());

2.理解代码中的内在逻辑(重点)

1)对语句i=j-1的一些分析

这里的关键在于——为什么进入了这个条件判断,或者说为什么跳出了循环2,从果到因(也可以称为归因分析1.0)。

是因为break,是因为a≠b导致循环终止,这说明对a的同一字符压缩操作已经完成,这时候应该对重复的b进行压缩了。

2.0 再换一种思路:为什么不是i=j?为什么不是i=j+1?

这个条件是为再次进入循环1做准备的,从上面划分的框架中可以看得出来。

a的压缩已经结束,那么第二次循环就是b的压缩,对b的压缩需要什么,需要注意索引的位置是否正确。

再次拿出这张图

由图不难看出,第二次进入循环的要求应该是i=3,这里结合延伸拓展里我所要补充的,一切似乎都可以串起来了。

进入循环之前会有一次i++的过程,所以在到for之前i应该是应该以i=2的形式作准备的,而到循环2第一次跳出时候i=0、j=3(如果纠结为什么是这样详见下篇),所以不难得出需要i=j-1这个语句了。

写到这里真的有一种很奇妙的感觉,在理解这些逻辑之前,我还在疑惑循环1不应该是从i=0、i=1...i=5这样逐个自增吗?可是代码最后就是很巧妙,再次进入循环之时,已经是i=3了,这就涉及算法中的效率问题了(可能是涉及时间复杂度(o)),算法的内在逻辑远远比我想象的高效。

2)关于int i = j的位置问题

为什么int i = j;不能能放在for循环的()之中呢?

在这段代码中,int j = i不能放在 for循环的初始化部分中,而需要单独放在外部的原因是因为j的赋值只需要在外层循环的开始时进行一次,而不是在每次内层循环的迭代中都重新初始化。

如果将int j = i 放在for循环的初始化部分中,那么在每次内层循环迭代时,j 的值都会被重新初始化为 i,这样就无法正确地实现内层循环中对连续重复字符的计数。我们希望的是在外层循环中初始化 j,然后在内层循环中保持 j 的值不变,直到遇到不同字符为止。

因此,将 `int j = i` 放在外部循环的起始位置是为了确保在内层循环中正确地保持 `j` 的值,以便正确计算连续重复字符的个数。

五、拓展延伸

思考:for循环中()中的i++在什么时候执行?

在Java中,for循环中括号中的部分通常被称为“循环控制表达式”或者“循环条件”。这部分表达式用于控制循环的执行次数或者条件。在for循环中,循环控制表达式通常包括三个部分,它们分别是初始化语句、循环条件和更新语句,用分号分隔开来。

具体来说:
- 初始化语句:在循环开始前执行,一般用于初始化循环变量。
- 循环条件:控制循环的执行,当条件为真时执行循环体,否则跳出循环。
- 更新语句:在每次循环结束后执行,用于更新循环变量的值,通常用于增加或减少循环变量的值。

for (initialization; condition; update) {
    // 循环体
}

因此,在 `for` 循环中,`i++` 在每次循环结束时执行。

写在最后,由于这只是一道题的思路、逻辑、解答整理,还是不要过于臃肿的好,这篇之后还会追加一个小短篇作为证明这段代码内在逻辑的一些证据(我会在一些关键语句上加上输出,在进行一个整体逻辑的梳理),算是对本篇的补充论证,争取以图文和代码相结合的形式呈现给大家,希望在阅读这篇后对题目的理解更深。作者与大家同进步!

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值