解决“压缩字符串”问题的编程过程详解

一、题目核心理解(直白说明)

这道题的要求很明确,就是对输入的字符串做“压缩”处理,但有两个关键规则必须遵守:

  1. 压缩格式:把连续相同的字符,改成“字符+出现次数”的形式。比如“AAABCCDDDD”,连续3个A就写成“A3”,1个B就直接写“B”(次数是1时不用写),2个C写成“C2”,4个D写成“D4”,最终压缩成“A3BC2D4”;
  2. 压缩前提:只有压缩后的字符串比原来的短,才输出压缩结果;如果压缩后长度不变甚至更长,就输出“NO”。比如输入“AB”,压缩后还是“AB”,长度一样,就输出“NO”。

输入是一行字符串(长度不超过500),输出要么是压缩后的字符串,要么是“NO”。

二、初始思路与第一次尝试(问题分析)

我一开始的想法是:找到连续字符的“结束位置”,把这个字符放到压缩字符串里,再统计连续次数。于是写了这样的代码片段:

for(int i = 0;str1[i]!='\0';i++)
{
  if(str1[i+1]==str1[i]&&str1[i+2]!=str1[i+1])
    {
      str2+=str1[i];
    }
}
// 还有一段统计次数的代码,用数组存次数

但这段代码运行后发现根本不对,问题出在判断“连续字符结束”的条件上——这个条件只能处理“刚好连续2个相同字符”的情况(比如“CC”),遇到连续3个及以上(比如“AAA”)就抓不住完整的结束位置,还会漏掉单个字符(比如“B”)。

比如用示例“AAABCCDDDD”测试,这段代码只能抓到“A”“C”“D”三个字符,漏掉了“B”,而且完全统计不出每个字符的连续次数,根本完不成压缩。

三、第一次优化:调整“连续字符结束”的判断条件

发现之前的判断条件太复杂,还容易出错,于是简化成:只要下一个字符和当前字符不同,就说明当前字符是“连续段的结束”,代码调整为:

for(int i = 0;str1[i+1]!='\0';i++)
{
  if(str1[i+1]!=str1[i])
    {
      str2+=str1[i];
    }
}

这个调整有明显进步:能正确抓到每个连续段的结束位置(比如“AAAB”里A的结束、B的结束),不会再漏单个字符了。但新问题又出现了:

  1. 遍历不完整:循环条件是“str1[i+1]!=’\0’”,意思是“只要下一个字符不是结束符就继续”,所以最后一个字符的下一个是结束符,循环会提前终止,导致最后一个连续段的字符(比如示例里的“D”)漏掉;
  2. 还是没统计次数:只能把字符放到压缩字符串里,却不知道每个字符连续出现了几次,没法完成“字符+次数”的压缩格式。

四、第二次优化:补全“计数+收尾+长度判断”逻辑

针对上面的两个问题,我做了三个关键补充,最终形成了能正确运行的代码:

  1. 加一个“计数变量”count:统计当前字符连续出现的次数,初始值设为1(因为每个字符至少出现1次);
  2. 处理最后一个字符:循环结束后,手动把最后一个字符加到压缩字符串里,并补充它的次数;
  3. 比较压缩前后长度:判断是否需要输出压缩结果或“NO”。

完整代码如下:

//#include <iostream>
//#include<string>
//
//using namespace std;
//int main()
//{
//  string str1;
//  string str2;
//  int arr[500];
//  int j=0;
//  cin>>str1;
//  for(int i = 0;str1[i+1]!='\0';i++)
//  {
//    if(str1[i+1]!=str1[i])
//      {
//        str2+=str1[i];
//      }
//    
//  }
//  for(int i = 0;str1[i]!='\0';i++)
//  {
//    if(str1[i+1]==str1[i])
//      {
//        arr[j]++;
//        if(str1[i+2]!=str1[i+1])
//        {
//          j++;
//        }
//      }
//  }
//  for(int m=0;m<j;m++)
//  cout<<str2[j]<<arr[j]<<endl;
//  return 0;
//}

#include <iostream>
#include <string>
using namespace std;

int main() {
    string str1, str2;
    cin >> str1;
    int n = str1.size(); // 原字符串长度

    // 特殊情况:长度≤1,直接输出NO
    if (n <= 1) {
        cout << "NO" << endl;
        return 0;
    }

    int count = 1; // 统计连续次数,初始为1
    // 你的核心循环:遍历到倒数第二个字符(因为判断str1[i+1])
    for (int i = 0; str1[i+1] != '\0'; i++) 
        {
        if (str1[i+1] != str1[i])
                 {
            // 1. 加入当前字符
            str2 += str1[i];
            // 2. 次数>1时,加入数字
            if (count > 1) 
                        {
                str2 += to_string(count);
            }
            // 3. 重置计数
            count = 1;
        } 
                else
                         {
                    // 下一个字符相同,计数+1
                    count++;
                }
    }        

    // 补全:处理最后一个字符(循环漏掉的部分)
    str2 += str1.back(); // 取最后一个字符
    if (count > 1) {
        str2 += to_string(count);
    }

    // 判断压缩后是否更短,决定输出
    if (str2.size() < n) {
        cout << str2 << endl;
    } else {
        cout << "NO" << endl;
    }

    return 0;
}

五、完整代码的执行过程( step by step 说明)

以示例输入“AAABCCDDDD”(长度9,字符依次是A、A、A、B、C、C、D、D、D、D)为例,一步步看代码怎么工作:

  1. 初始状态:str1是输入的字符串,str2为空,count=1,n=9;
  2. 循环遍历(i从0开始,直到str1[i+1]是结束符):
    1. i=0:str1[1]是A,和当前A相同 → count变成2;
    2. i=1:str1[2]是A,和当前A相同 → count变成3;
    3. i=2:str1[3]是B,和当前A不同 → 把A加到str2,count=3>1,再把“3”加到str2(此时str2是“A3”),count重置为1;
    4. i=3:str1[4]是C,和当前B不同 → 把B加到str2(str2是“A3B”),count=1不用加数字,count重置为1;
    5. i=4:str1[5]是C,和当前C相同 → count变成2;
    6. i=5:str1[6]是D,和当前C不同 → 把C加到str2,count=2>1,加“2”(str2是“A3BC2”),count重置为1;
    7. i=6:str1[7]是D,相同 → count=2;
    8. i=7:str1[8]是D,相同 → count=3;
    9. i=8:str1[9]是结束符 → 循环终止;
  3. 补全最后一个字符:str1.back()是最后一个D,此时count=4>1 → 把D和“4”加到str2(str2变成“A3BC2D4”);
  4. 长度比较:压缩后str2长度是6,比原长度9短 → 输出“A3BC2D4”。

六、核心知识点详解(重点讲,讲明白)

知识点1:计数变量count的作用与使用规则

  • 作用:专门统计“当前字符连续出现了几次”;
  • 初始值设为1的原因:每个字符至少出现1次,比如第一个字符A,刚遇到时就已经出现1次了;
  • 变化规则:遇到和当前字符相同的,count就加1;遇到不同的,就重置为1(开始统计下一个字符)。

知识点2:字符串的遍历与边界处理

  • 循环条件“str1[i+1]!=’\0’”:意思是“只要下一个字符不是结束符,就继续遍历”,这样能避免遍历到最后一个字符时,访问str1[i+1]超出字符串范围(字符串的有效字符范围是从第一个到最后一个,结束符不算有效字符);
  • 为什么要补全最后一个字符:因为循环在“最后一个字符的前一个字符”就终止了,最后一个字符没被处理,所以要用str1.back()把它取出来,补充到压缩字符串里。

知识点3:str1.back()函数(取最后一个字符)

  • 作用:直接获取字符串的最后一个有效字符,不用手动算下标(比如str1.size()-1是最后一个字符的下标,back()相当于直接用这个下标取值);
  • 头文件:包含在<string>里(代码开头已经包含);
  • 示例:str1是“AAABCCDDDD”,str1.back()就是最后一个字符“D”。

知识点4:to_string()函数(把数字转成字符串)

  • 作用:把整数(比如3、4)转换成对应的字符串(比如“3”、“4”);
  • 为什么需要它:C++里,字符(比如’A’)和整数(比如3)不能直接拼在一起,必须把整数变成字符串才能拼接;
  • 头文件:包含在<string>里;
  • 示例:to_string(3) → 得到字符串“3”,这样才能和’A’拼成“A3”。

知识点5:字符串拼接操作(+=)

  • 作用:把字符或字符串,追加到另一个字符串的末尾;
  • 用法:
    • 字符拼接:str2 += str1[i] → 把str1[i]这个字符加到str2最后;
    • 字符串拼接:str2 += to_string(count) → 把转换后的数字字符串加到str2最后;
  • 示例:str2是“A3”,执行str2 += ‘B’后,变成“A3B”;再执行str2 += “2”后,变成“A3B2”。

知识点6:字符串长度比较(size()函数)

  • 作用:size()函数获取字符串的“有效字符个数”(结束符不算);
  • 用法:str1.size()是原字符串长度,str2.size()是压缩后长度;
  • 判断规则:只有str2.size() < str1.size()时,才输出压缩结果,否则输出“NO”(保证压缩能节省空间)。

知识点7:特殊情况处理(字符串长度≤1)

  • 为什么直接输出NO:当字符串长度是1时,只有一个字符,压缩后还是这个字符,长度不变;空字符串(长度0)更没法压缩,所以这两种情况都直接输出NO。

七、总结(解决问题的核心思路与经验)

1. 核心解题思路

压缩字符串的关键是“找连续字符段→统计次数→按规则拼接”,步骤可以简化为:

  1. 遍历字符串,用count统计每个连续字符的次数;
  2. 遇到不同字符时,把当前字符和次数(次数>1时)拼接到压缩字符串;
  3. 处理最后一个字符(循环漏处理的);
  4. 比较长度,决定输出压缩结果还是NO。

2. 初学易犯的错误与避免方法

  • 错误1:计数从0开始 → 避免方法:记住count初始值必须是1;
  • 错误2:忘记补全最后一个字符 → 避免方法:只要循环条件是判断str1[i+1],就一定要在循环结束后处理最后一个字符;
  • 错误3:把次数1也拼到字符串里 → 避免方法:严格判断count>1时才用to_string()拼接;
  • 错误4:不比较长度直接输出 → 避免方法:最后一定要用size()比较,符合“压缩后更短”的条件才输出压缩结果。

3. 经验总结

解决这类“字符串统计/压缩”问题,核心是“找准连续段的边界”和“准确统计次数”,用一个计数变量就能搞定次数统计,再注意处理好字符串的边界(比如最后一个字符),就能避免大部分错误。这次从最初的复杂判断条件,逐步简化优化,补全缺失的逻辑,最终得到正确代码的过程,也让我明白:编程题不用一开始就追求完美,先搭好核心框架,再逐步解决遇到的问题,就能慢慢接近正确答案。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇