C++双指针实现原地字符数组压缩:行程长度编码算法详解
1. 项目概述为什么字符数组压缩值得深究在C的日常开发中处理字符串和字符数组是家常便饭。无论是日志系统、网络协议还是简单的配置文件解析我们常常会遇到包含大量重复连续字符的数据。比如一个传感器日志可能是AAABBBCCCDDD或者用户输入了一串aaaabbbcccaa。直接存储或传输这些原始数据不仅占用宝贵的存储空间在网络传输中也会消耗更多的带宽和时间。这时候一个简单高效的压缩算法就显得尤为重要。字符数组压缩或者说字符串压缩其核心目标就是用更少的空间来表示相同的信息。这听起来像是数据压缩领域的宏大课题但我们可以从一个非常经典且实用的算法入手行程长度编码。这个算法的思想朴素而强大——对于任何连续重复出现的字符我们不存储每一个字符而是存储该字符以及它连续出现的次数。例如字符串AAABBBCCCDDD可以被压缩表示为A3B3C3D3。从12个字符缩减到8个字符效果立竿见影。这个项目标题点出了两个关键技术点字符串压缩和双指针。双指针是解决这类“原地修改数组/字符串”问题的利器它能在O(n)的时间复杂度和O(1)的额外空间复杂度下优雅地完成任务。对于C开发者而言掌握这种算法不仅是解决特定问题更是对指针操作、数组边界管理和就地算法设计能力的一次绝佳锻炼。无论你是正在准备面试还是希望优化手头项目的某个数据处理模块这个“简单高效的方法”都值得你花时间彻底搞懂。2. 核心思路与算法选型为什么是“原地”双指针当我们决定压缩一个字符数组时首先面临一个设计选择是创建一个新的数组/字符串来存放压缩结果还是在原数组上“就地”修改两种方案各有优劣。方案一创建新容器。这是最直观的思路。我们遍历原数组将压缩后的字符和计数依次追加到一个新的std::string或std::vectorchar中。这种方法安全、清晰不易出错因为读写操作是分离的。但是它的空间复杂度是O(n)需要额外分配内存。在某些内存受限的嵌入式环境或者题目明确要求原地修改输入数组的场景下这个方案就不适用了。方案二原地修改。这正是本项目标题所暗示的“高效”所在。我们直接在输入的字符数组上进行读写操作使用两个指针索引来追踪位置写指针write_idx指向下一个压缩结果应该写入的位置。读指针read_idx或通过循环变量i隐式表示指向当前正在处理的原始字符的位置。这个过程就像是在整理一个抽屉write_idx是整理后物品的摆放位置而read_idx是我们在翻找的原始物品。我们一边查看原始物品读一边将整理好的物品放回抽屉前端写。这样做最大的好处是空间复杂度为O(1)除了几个临时变量不需要任何额外空间。这对于追求极致效率的场景至关重要。那么为什么双指针能完美适配“行程长度编码”呢因为该算法的核心是寻找连续相同字符的片段。双指针中的读指针可以轻松地扫描并确定一个片段的结束而写指针则负责将片段信息字符和计数写回数组前端。这是一个经典的“快慢指针”或“读写指针”应用场景。注意原地修改算法需要特别注意输入数组的长度。压缩后的结果长度一定小于或等于原数组长度在最坏情况即没有连续重复字符时长度可能翻倍例如“abc”变成“a1b1c1”。因此在实际工程中必须确保传入的数组有足够的空间容纳可能变长的结果或者更常见的题目/接口会保证数组长度足够。在我们的讨论中我们假设提供的字符数组空间充足。3. 算法实现细节与C实操要点理解了核心思路我们来一步步拆解如何用C实现这个原地压缩算法。我们将处理一个std::vectorchar作为字符数组并返回压缩后的新长度。3.1 基础框架与双指针初始化首先处理边界情况。如果输入数组为空压缩结果自然也是空的。int compress(std::vectorchar chars) { int n chars.size(); if (n 0) return 0; int write_idx 0; // 下一个压缩字符要写入的位置 // 读指针 i 将在循环中体现 }这里write_idx初始化为0意味着我们将从数组的第一个位置开始写入压缩结果。3.2 核心循环定位连续字符片段接下来我们用一个for循环来遍历整个数组。循环变量i就是我们的读指针。for (int i 0; i n; ) { char current_char chars[i]; int count 0; // 内层循环统计当前字符连续出现的次数 while (i n chars[i] current_char) { count; i; // i 在这里既是判断依据也是移动的读指针 } // 此时i 指向下一个不同字符的开始位置或数组末尾 // count 存储了 current_char 连续出现的次数 }内层的while循环是算法的关键。只要i没有越界并且当前字符等于我们正在统计的current_char我们就增加计数并移动i。这个循环结束后我们就得到了一个完整的连续字符片段。3.3 写入压缩结果字符与数字的处理获得字符和计数后我们需要将其写回chars数组的write_idx位置。// 第一步写入字符本身 chars[write_idx] current_char; write_idx; // 第二步如果计数大于1需要将计数转换成字符写入 if (count 1) { // 将整数 count 转换为字符串例如 12 - 12 std::string count_str std::to_string(count); for (char c : count_str) { chars[write_idx] c; write_idx; } }这里有三个非常重要的细节字符总是要写入的无论它重复了多少次。即使只出现一次count 1我们也要写入这个字符。数字仅在计数大于1时写入。这是行程长度编码的通用约定“ab”压缩后应该是“ab”而不是“a1b1”否则对于无重复的字符串反而会“压缩”得更长。计数可能有多位。比如某个字符连续出现了12次我们需要写入字符‘1’和‘2’。使用std::to_string可以方便地将整数转换为字符串然后逐个字符写入。这是原地算法中一个容易忽略的细节。3.4 循环结束与返回值当外层for循环结束时意味着整个原始数组都被处理完毕。此时write_idx的值恰好就是压缩后新数组的长度。我们需要返回这个长度。// 循环结束后write_idx 就是压缩数组的新长度 return write_idx;为什么返回长度而不是直接返回数组这是原地算法接口设计的常见方式。调用者根据返回的长度可以知道chars数组中前write_idx个元素是有效的压缩结果。数组write_idx之后的位置可能还存留着旧的、未被覆盖的数据但它们已经被视为无效。3.5 完整代码示例将以上部分组合起来就得到了完整的解决方案#include vector #include string int compress(std::vectorchar chars) { int n chars.size(); int write_idx 0; for (int i 0; i n; ) { char current_char chars[i]; int count 0; // 统计相同字符的连续个数 while (i n chars[i] current_char) { count; i; } // 写入字符 chars[write_idx] current_char; // 如果计数大于1写入数字 if (count 1) { for (char c : std::to_string(count)) { chars[write_idx] c; } } } // 返回压缩后数组的新长度 return write_idx; }4. 复杂度分析与边界条件处理一个健壮的算法实现离不开对性能和边界的清晰认识。时间复杂度O(n)。尽管代码中有嵌套循环但每个字符只被读指针i访问一次也被写指针write_idx访问一次写入字符或数字。因此总操作次数与输入数组长度n成线性关系。空间复杂度O(1)。我们只使用了固定数量的额外变量n,write_idx,i,current_char,count以及临时字符串count_str所占用的栈空间该字符串长度由计数位数决定最大为log10(n)通常视为常数。符合原地修改的要求。关键边界条件与陷阱单个字符的处理如前所述计数为1时不写入数字。这是算法正确性的基础务必注意。数字的多位处理使用std::to_string是最安全便捷的方式。自己实现数字转字符串时要注意逆序写入的问题。数组越界虽然我们假设数组空间足够但在while循环中仍需判断i n这是良好的编程习惯。返回值的使用调用此函数后应该只使用chars的前compress(chars)个元素。例如std::vectorchar data {a,a,b,b,c,c,c}; int new_len compress(data); // 现在data 的前 new_len 个字符是压缩结果{a,2,b,2,c,3} // 可以使用 data.resize(new_len) 来截断数组只保留有效部分。5. 测试用例与调试技巧理论再完美也需要经过测试的检验。设计全面的测试用例是确保算法鲁棒性的关键。基础测试用例空数组{}- 返回0数组不变。无重复字符{‘a’ ‘b’ ‘c’}- 应返回3数组变为{‘a’ ‘b’ ‘c’}因为计数为1不写入数字。全重复字符{‘a’ ‘a’ ‘a’}- 应返回2数组变为{‘a’ ‘3’}。混合情况{‘a’ ‘a’ ‘b’ ‘b’ ‘b’ ‘c’}- 应返回6数组变为{‘a’ ‘2’ ‘b’ ‘3’ ‘c’}。进阶测试用例容易出错计数为两位数或更多{‘a’} * 1212个’a’ - 应返回3数组变为{‘a’ ‘1’ ‘2’}。这是检验数字转换是否正确的好例子。紧跟数字的字符原始数组末尾本身就可能有数字字符如{‘2’ ‘2’}。算法应能正确识别这是两个字符’2’压缩为{‘2’ ‘2’}因为计数为2写入数字’2’。测试时需确认结果是否符合预期。调试技巧在实现过程中可以在关键步骤后打印数组状态和指针位置这是最直接的调试方法。// 在写入字符和数字后可以临时打印查看 std::cout “After processing char “ current_char “: “; for (int k 0; k write_idx; k) std::cout chars[k]; std::cout std::endl;另外务必使用IDE的调试器如VS Code、CLion、Visual Studio的调试功能单步执行并观察变量i、write_idx、count的变化这能帮你直观理解双指针的移动逻辑。6. 扩展思考与工程化应用掌握了基础算法后我们可以思考一些更深入的问题和实际应用场景。1. 算法变体如果要求压缩格式为“字符计数”即使计数为1也写入这只需要移除if (count 1)的判断始终写入计数字符串即可。但需要注意这可能导致输出长度超过输入长度例如“abc”-“a1b1c1”。在工程中这通常不是最优选择因为它失去了压缩的意义。2. 性能优化点std::to_string会生成一个新的std::string对象涉及内存分配。在极端追求性能的场景下可以预先分配一个足够大的字符数组比如20位对应最大计数然后使用sprintf或自定义的整数转字符函数将数字填入避免动态内存分配。但大多数情况下std::to_string的简洁性和可读性优势更大。如果输入数据规模巨大GB级别且重复片段非常长例如连续几万个相同字符当前算法依然是O(n)的但内存访问模式是顺序的对CPU缓存友好性能已经很好。3. 工程化应用场景日志文件压缩服务器日志中经常有大量重复的时间戳前缀或状态码。可以在写入日志文件前对每行日志应用简单的行程长度编码进行压缩。简单位图压缩对于二值图像如黑白传真每一行可以看作由连续的黑色像素和白色像素组成非常适合用行程长度编码压缩。这就是经典的RLE图像压缩格式。网络协议优化在自定义的轻量级通信协议中对于某些重复出现的命令字或状态字段可以采用类似的压缩思路减少数据包大小。4. 与标准库的结合在实际C项目中我们处理的可能是std::string而非std::vectorchar。算法逻辑完全一致因为std::string也支持下标访问和修改。只需要注意std::string的size()和[]操作即可。int compressString(std::string s) { int n s.size(); int write_idx 0; for (int i 0; i n; ) { char cur s[i]; int cnt 0; while (i n s[i] cur) { cnt; i; } s[write_idx] cur; if (cnt 1) { for (char c : std::to_string(cnt)) { s[write_idx] c; } } } s.resize(write_idx); // string可以方便地resize return write_idx; }这个“简单高效”的字符数组压缩方法完美诠释了双指针技术在解决原地修改问题上的优雅与力量。它不要求你掌握高深的压缩理论而是用清晰的逻辑和扎实的C基本功解决了一个实际开发中可能遇到的性能痛点。下次当你面对一串充满重复的数据时不妨试试这个思路或许就能为你的系统带来意想不到的效率提升。

相关新闻

当AI学会“倾听“:一颗37毫米芯片如何重塑全双工语音的纯净边界

当AI学会“倾听“:一颗37毫米芯片如何重塑全双工语音的纯净边界

引言:在喧嚣世界中,寻找最纯粹的声音我们生活在一个被声音淹没的时代——车流轰鸣的街头、嘈杂的工地旁、人声鼎沸的会议厅里,每一次远程通话都在与噪声进行无声的博弈。回音、风噪、瞬态噪音,这些无形的干扰者让语音交互的体验大…

2026/7/29 8:19:03阅读更多 →
RK3568 Android 15驱动开发实战:从环境搭建到HAL适配

RK3568 Android 15驱动开发实战:从环境搭建到HAL适配

如果你正在寻找一套真正能让你从零开始掌握RK3568 Android驱动开发的实战教程,那么蔡工的这个新课程可能正是你需要的。很多开发者面对RK3568这样的高性能嵌入式平台时,往往卡在驱动开发这一关——官方文档零散、示例代码不完整、调试过程充满未知。这个…

2026/7/29 8:19:03阅读更多 →
OpenRouter实战指南:统一AI大模型API,解决多模型集成开发痛点

OpenRouter实战指南:统一AI大模型API,解决多模型集成开发痛点

1. 项目概述:当AI大模型成为“水电煤”,集成开发为何仍是痛点? 如果你最近在折腾AI应用开发,尤其是想把ChatGPT、Claude、DeepSeek这些不同的大模型能力集成到自己的产品里,那你大概率已经体会过什么叫“甜蜜的烦恼”。…

2026/7/29 8:19:03阅读更多 →
LangChain Skills架构实战:电商客服Agent优化与性能提升

LangChain Skills架构实战:电商客服Agent优化与性能提升

1. 项目概述:LangChain Skills架构实战精要在AI应用开发领域,LangChain已成为连接大语言模型与实际业务场景的桥梁型框架。最近三个月,我在多个企业级项目中深度应用了LangChain的Skills架构,特别是在构建复杂Agent系统时&#xf…

2026/7/29 9:47:19阅读更多 →
lattice fpga芯片上电偶发性不工作

lattice fpga芯片上电偶发性不工作

问题原因:por时序不对,program管脚上电时被提前释放导致lattice上电启动异常

2026/7/29 9:47:19阅读更多 →
IDOR 接口漏洞引发定向钓鱼风险及 Node.js 防护体系研究

IDOR 接口漏洞引发定向钓鱼风险及 Node.js 防护体系研究

摘要 面向垂直领域的轻量化 Web 应用普遍存在开发安全流程缺失问题,不安全直接对象引用(IDOR)水平越权漏洞极易造成大规模用户个人信息泄露,泄露数据将成为定向钓鱼攻击的核心数据源。本文以梵蒂冈官方 Click To Pray 祷告 APP 安…

2026/7/29 9:47:19阅读更多 →
工业实训仿真设计实践:电机拆装软件的 DAG 流程建模、工具精度分级与数据体系搭建

工业实训仿真设计实践:电机拆装软件的 DAG 流程建模、工具精度分级与数据体系搭建

在职业教育工业虚拟仿真实训领域,电机拆装是极具代表性的标杆场景 —— 操作流程强步骤依赖、工具品类多、真实实操容错成本高,一款仿真软件的核心设计能力,往往能在这个场景中得到最直接的体现。本文以龙泽科技新能源汽车电机虚拟拆装仿真教…

2026/7/29 9:47:19阅读更多 →
Twelve South推新版Valet充电托盘:小体积大升级,功率提升价格更亲民!

Twelve South推新版Valet充电托盘:小体积大升级,功率提升价格更亲民!

Twelve South推新版Valet充电托盘,专为小空间设计今年早些时候在国际消费电子展(CES)上首次亮相的Twelve South皮革包裹式Valet充电托盘,如今推出了新版本。该版本专为空间有限的场所打造,原版深度为7.5英寸&#xff0…

2026/7/29 9:47:19阅读更多 →
手机端续接 Codex 实战:从安装 linco-connect 到跑通第一个跨端会话

手机端续接 Codex 实战:从安装 linco-connect 到跑通第一个跨端会话

摘要: 本文使用 Linco Bridge 的官方在线 Demo,完整走一遍“手机端生成连接配置→电脑安装并启动 linco-connect→确认 Codex 在线→发送第一条只读任务”的流程。文末附常见排障方法和安全边界。 关键词: Codex CLI、Linco Bridge、linco-co…

2026/7/29 9:45:18阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/28 2:35:58阅读更多 →