一、题目核心理解(直白说明)
这道题的要求很明确,就是对输入的字符串做“压缩”处理,但有两个关键规则必须遵守:
- 压缩格式:把连续相同的字符,改成“字符+出现次数”的形式。比如“AAABCCDDDD”,连续3个A就写成“A3”,1个B就直接写“B”(次数是1时不用写),2个C写成“C2”,4个D写成“D4”,最终压缩成“A3BC2D4”;
- 压缩前提:只有压缩后的字符串比原来的短,才输出压缩结果;如果压缩后长度不变甚至更长,就输出“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的结束),不会再漏单个字符了。但新问题又出现了:
- 遍历不完整:循环条件是“str1[i+1]!=’\0’”,意思是“只要下一个字符不是结束符就继续”,所以最后一个字符的下一个是结束符,循环会提前终止,导致最后一个连续段的字符(比如示例里的“D”)漏掉;
- 还是没统计次数:只能把字符放到压缩字符串里,却不知道每个字符连续出现了几次,没法完成“字符+次数”的压缩格式。
四、第二次优化:补全“计数+收尾+长度判断”逻辑
针对上面的两个问题,我做了三个关键补充,最终形成了能正确运行的代码:
- 加一个“计数变量”count:统计当前字符连续出现的次数,初始值设为1(因为每个字符至少出现1次);
- 处理最后一个字符:循环结束后,手动把最后一个字符加到压缩字符串里,并补充它的次数;
- 比较压缩前后长度:判断是否需要输出压缩结果或“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)为例,一步步看代码怎么工作:
- 初始状态:str1是输入的字符串,str2为空,count=1,n=9;
- 循环遍历(i从0开始,直到str1[i+1]是结束符):
- i=0:str1[1]是A,和当前A相同 → count变成2;
- i=1:str1[2]是A,和当前A相同 → count变成3;
- i=2:str1[3]是B,和当前A不同 → 把A加到str2,count=3>1,再把“3”加到str2(此时str2是“A3”),count重置为1;
- i=3:str1[4]是C,和当前B不同 → 把B加到str2(str2是“A3B”),count=1不用加数字,count重置为1;
- i=4:str1[5]是C,和当前C相同 → count变成2;
- i=5:str1[6]是D,和当前C不同 → 把C加到str2,count=2>1,加“2”(str2是“A3BC2”),count重置为1;
- i=6:str1[7]是D,相同 → count=2;
- i=7:str1[8]是D,相同 → count=3;
- i=8:str1[9]是结束符 → 循环终止;
- 补全最后一个字符:str1.back()是最后一个D,此时count=4>1 → 把D和“4”加到str2(str2变成“A3BC2D4”);
- 长度比较:压缩后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. 核心解题思路
压缩字符串的关键是“找连续字符段→统计次数→按规则拼接”,步骤可以简化为:
- 遍历字符串,用count统计每个连续字符的次数;
- 遇到不同字符时,把当前字符和次数(次数>1时)拼接到压缩字符串;
- 处理最后一个字符(循环漏处理的);
- 比较长度,决定输出压缩结果还是NO。
2. 初学易犯的错误与避免方法
- 错误1:计数从0开始 → 避免方法:记住count初始值必须是1;
- 错误2:忘记补全最后一个字符 → 避免方法:只要循环条件是判断str1[i+1],就一定要在循环结束后处理最后一个字符;
- 错误3:把次数1也拼到字符串里 → 避免方法:严格判断count>1时才用to_string()拼接;
- 错误4:不比较长度直接输出 → 避免方法:最后一定要用size()比较,符合“压缩后更短”的条件才输出压缩结果。
3. 经验总结
解决这类“字符串统计/压缩”问题,核心是“找准连续段的边界”和“准确统计次数”,用一个计数变量就能搞定次数统计,再注意处理好字符串的边界(比如最后一个字符),就能避免大部分错误。这次从最初的复杂判断条件,逐步简化优化,补全缺失的逻辑,最终得到正确代码的过程,也让我明白:编程题不用一开始就追求完美,先搭好核心框架,再逐步解决遇到的问题,就能慢慢接近正确答案。