从L1-027出租题解看暴力算法在C++中的实践与优化

发布时间:2026/10/20 17:46:32

从L1-027出租题解看暴力算法在C++中的实践与优化 1. 从一道题看“暴力”的智慧L1-027 出租的解题思路最近在带新人刷题又看到了PTA程序设计类实验辅助教学平台上这道经典的L1-027“出租”。题目本身不难但很有意思它像一面镜子能清晰地照出一个程序员在面对问题时的第一反应和思维深度。很多人看到“出租”这个标题可能一头雾水其实它讲的是给出一串11位的手机号码你需要从中提取出所有不重复的数字然后按从大到小排序形成一个“索引数组”接着根据这个索引数组为手机号码的每一位数字生成一个“出租”序列号。题目描述有点绕但核心就是数组操作、去重和排序。网上搜一下解法五花八门但“C暴力实现”这个关键词出现的频率相当高。这引发了我的思考在算法竞赛和日常开发中“暴力”究竟意味着什么是无奈之选还是特定场景下的最优解今天我们就以这道题为引子不单单是给出答案更要深挖一下所谓“暴力实现”背后的代码逻辑、性能考量以及它所能教会我们的编程思维。你会发现有时候最直接的方法恰恰是理解问题本质最快、最稳的路径。2. 题目拆解与“暴力”逻辑的建立在动手写代码之前彻底理解题目要求是避免反复调试、写出混乱代码的关键。L1-027的完整描述需要去平台查看但其核心逻辑可以拆解为两个明确的阶段这本身就是一种“解题暴力”——用最直白的方式分解任务。2.1 第一阶段构建数字映射表索引数组输入是一个字符串格式的手机号码比如13588618832。 第一步我们需要从中找出所有出现过的数字。注意是数字字符‘0‘-’9‘而不是数字本身。去重后我们得到一个数字集合例如从上述号码中可以得到{‘1‘, ’3‘, ’5‘, ’8‘, ’6‘, ’2‘}。 第二步题目要求将这个集合里的数字按照从大到小的顺序排列。所以{‘1‘, ’3‘, ’5‘, ’8‘, ’6‘, ’2‘}排序后变成{‘8‘, ’6‘, ’5‘, ’3‘, ’2‘, ’1‘}。 第三步也是最关键的一步我们需要建立一个映射关系数字 - 该数字在排序后数组中的下标索引。这个索引数组就是题目输出的第一行。通常我们会用另一个数组int index[10]来记录index[digit]的值就是数字digit在排序数组中的位置如果该数字没出现过则置为-1或其他标记值。注意这里有一个初学者极易混淆的点。排序后数组的下标是从0开始的。所以对于排序数组arr[] {8,6,5,3,2,1}数字8的索引是0数字6的索引是1以此类推。这个映射关系是后续编码的基础。2.2 第二阶段生成出租序列有了上面的映射表第二步就简单了。遍历原始手机号码的每一个数字字符去查表index数组找到它对应的索引值然后按格式输出。这就是题目要求的第二行输出。所谓的“暴力实现”在这个场景下指的就是不刻意追求奇技淫巧而是严格按照上述人脑思考的步骤用C最基本、最直观的语法特性来实现。它可能不会用到std::set自动去重排序也可能不会用std::map来构建映射而是用手动遍历、数组标记等“原始”手段。但这恰恰是理解数据流动和控制流程的最佳方式。3. 一种典型的“暴力”C实现与逐行精讲接下来我们看一段完全按照上述“暴力”思维实现的代码。我会逐段分析并解释每一行代码背后的意图以及为什么这么写。#include iostream #include string #include algorithm using namespace std; int main() { string phone; cin phone; // 读入手机号字符串 // 步骤1标记出现过的数字 bool appeared[10] {false}; // 下标0-9初始化为false for (char c : phone) { int digit c - 0; // 将字符0-9转换为整数0-9 appeared[digit] true; } // 步骤2收集出现过的数字并存入数组以待排序 int uniqueDigits[10]; int count 0; for (int i 0; i 10; i) { if (appeared[i]) { uniqueDigits[count] i; count; } } // 步骤3对收集到的数字进行从大到小排序 // 使用标准库sort但需要自定义比较函数实现降序 sort(uniqueDigits, uniqueDigits count, greaterint()); // 步骤4构建索引映射表 int indexMap[10]; // indexMap[数字] 该数字在uniqueDigits中的下标 // 先初始化为-1表示未出现 for (int i 0; i 10; i) { indexMap[i] -1; } // 遍历排序后的数组填充映射关系 for (int i 0; i count; i) { int digit uniqueDigits[i]; indexMap[digit] i; // 数字digit的索引是i } // 步骤5输出索引数组第一行 cout int[] arr new int[]{; for (int i 0; i count; i) { if (i ! 0) cout ,; cout uniqueDigits[i]; } cout }; endl; // 步骤6输出手机号的出租序列第二行 cout int[] index new int[]{; for (int i 0; i phone.length(); i) { if (i ! 0) cout ,; int digit phone[i] - 0; cout indexMap[digit]; } cout }; endl; return 0; }代码精讲与“暴力”之处bool appeared[10]这是最“暴力”的标记法。我们只关心0-9这10个数字是否出现所以直接开一个长度为10的布尔数组。遍历手机号时将对应位置标记为true。空间复杂度O(1)极其高效直观。这就是“暴力”的智慧——在问题规模明确且很小时用最直接的数据结构。手动收集与排序我们并没有使用可以自动去重和排序的容器如set而是先标记再通过一次遍历appeared数组将出现过的数字i放入uniqueDigits。然后使用sort进行排序。这里greaterint()是一个函数对象用于实现降序排序。这个过程完全模拟了手工操作的步骤。indexMap的构建这是映射的核心。我们先用-1初始化整个数组因为数字0-9都可能出现未出现的应有一个特殊值。然后遍历uniqueDigits对于排序数组中第i位的数字digit令indexMap[digit] i。这样查询时就是O(1)的时间复杂度。输出格式严格遵循题目要求的类Java数组输出格式。注意逗号的处理通过if (i ! 0)来控制避免末尾出现多余的逗号这是处理格式化输出的常见技巧。这段代码没有使用任何高级的STL容器算法除了sort完全用基础数组和循环完成逻辑链条清晰非常适合初学者理解每一步在做什么。它“暴力”地实现了所有步骤但正因为如此它的可控性和可理解性极高。4. 从“暴力”到“优化”不同解法的对比与思考理解了基础暴力解法我们来看看其他常见的实现思路并分析它们与“暴力”法的异同。这能帮助我们在不同场景下做出更合适的选择。4.1 使用set进行自动去重排序这是很多熟悉STL的开发者会首先想到的方法。#include iostream #include set #include string #include algorithm using namespace std; int main() { string phone; cin phone; setchar, greaterchar digitSet; // 降序set for (char c : phone) { digitSet.insert(c); } // 将set转为vector或数组以便索引 vectorchar uniqueDigits(digitSet.begin(), digitSet.end()); // 构建映射表 int indexMap[10] {-1}; for (int i 0; i uniqueDigits.size(); i) { int digit uniqueDigits[i] - 0; indexMap[digit] i; } // ... 输出部分与暴力法类似 }对比分析优点代码更简洁set自动保证了元素的唯一性和顺序通过greaterchar指定降序省去了手动标记、收集、排序的步骤。缺点对于这道题set的插入操作是O(log n)虽然n最大为11可以忽略不计但理论上比直接数组标记的O(n)要慢。更重要的是它引入了一层抽象对于初学者来说可能不如数组标记法那样能清晰地展现“去重”和“排序”这两个独立的过程。思考set解法更“声明式”你告诉程序“我要一个降序且不重复的集合”程序自己去实现。而暴力解法是“命令式”的你一步步指挥程序怎么做。在算法题中理解命令式做法往往对夯实基础更有帮助。4.2 使用map或哈希表构建映射我们也可以边遍历边建立映射。// 一种思路先得到排序去重的数字列表然后用map vectorint digits; // 假设已获得降序排序的数字列表 unordered_mapint, int digitToIndex; for (int i 0; i digits.size(); i) { digitToIndex[digits[i]] i; } // 查询时int idx digitToIndex[phone[i]-0];对比分析优点映射关系表达非常直接map或unordered_map的语义就是键值对。缺点对于键值范围固定且很小0-9的情况使用数组indexMap是更高效、更节省空间的选择。数组的随机访问是O(1)而map通常基于红黑树O(log n)unordered_map基于哈希表平均O(1)但有哈希冲突和扩容开销。核心启示这就是“暴力”中蕴含的优化思想。当数据范围已知且有限时用数组替代关联容器常常能获得常数级别的性能提升和更低的内存开销。这是竞赛编程和性能敏感开发中的一个重要技巧。4.3 “暴力”的边界与优化空间我们的“暴力”实现还有优化空间吗当然有。比如我们可以将步骤1标记、步骤2收集、步骤4构建映射进行更紧密的融合。一种更紧凑的写法是在从大到小遍历数字9到0的过程中如果该数字在appeared中为真则将其加入uniqueDigits同时它的索引就是当前uniqueDigits的大小因为我们是按降序加入的。这样排序的步骤可以省略因为我们遍历的顺序就是降序收集和建映射可以在一次循环中完成。int uniqueDigits[10], indexMap[10]; int count 0; for (int digit 9; digit 0; --digit) { // 从大到小遍历 if (appeared[digit]) { uniqueDigits[count] digit; indexMap[digit] count; // 建立映射 count; } } // 此时uniqueDigits已经是降序排列无需再调用sort这个版本减少了sort的调用逻辑更精炼。它依然是“暴力”的思路直接但更高效。这告诉我们“暴力”不等于“笨拙”在理解问题的基础上我们可以写出既直接又高效的“暴力”代码。5. 常见“坑点”与调试心得即便思路清晰实现这道题时还是会遇到一些典型的坑。这里分享几个我见过或自己踩过的坑以及调试方法。5.1 字符与整数的转换陷阱这是最经典的错误之一。phone[i]是一个字符比如‘5‘。它的ASCII码是53。如果你直接写int digit phone[i];那么digit的值是53而不是5。这会导致数组越界访问appeared[53]或逻辑错误。正确做法int digit phone[i] - 0;。因为字符‘0‘-’9‘在ASCII表中是连续的减去‘0‘就得到了对应的整数值。5.2 索引映射表的初始化问题在构建indexMap时必须初始化。如果我们只给出现过的数字赋值那么未出现的数字对应的indexMap值就是未定义的可能是任意值。在后续查询时如果程序逻辑有误比如手机号字符串里混入了非数字字符虽然本题保证不会或者用来做其他判断就会出错。稳健做法用-1或一个不可能作为有效索引的值如-1来初始化整个indexMap数组。这样查询时如果得到-1就能立刻发现数据有问题。5.3 输出格式的严格匹配PTA等在线判题系统对输出格式的要求是极其严格的多一个空格、少一个逗号、标点符号是全角还是半角都会导致“格式错误”。本题要求输出两行每行形如int[] arr new int[]{8,6,5,3,2,1};。调试技巧先不要管格式用cout uniqueDigits[i] 这样的方式输出确保核心数据正确。数据正确后再严格按照格式调整。对于逗号采用“非首元素前加逗号”的模式if(i ! 0) cout ,;。终极调试法将你的输出和题目样例的输出复制到一个文本比较工具或IDE的差分比较中肉眼逐字符对比很容易发现空格、标点的差异。5.4 边界条件所有数字都相同考虑一个极端情况手机号是11111111111。那么出现过的数字只有{1}。排序后数组是{1}索引映射为indexMap[1]0。输出时第一行是int[] arr new int[]{1};第二行是11个0。我们的代码能正确处理吗可以。因为appeared数组只有下标1为真uniqueDigits只收集到1indexMap[1]被赋值为0。遍历手机号时每个数字1都映射到0。这是一个很好的自测用例。6. 举一反三“暴力”思维在算法学习中的价值通过L1-027这道题我们可以更深入地思考“暴力”在编程中的角色。1. “暴力”是理解问题的起点。在面对一个新问题时最先想到的、最符合直觉的解法往往就是暴力解法。它强迫你把问题的输入、输出、中间过程想清楚。就像解数学题先列出所有已知条件一样。跳过暴力解法直接追求“最优解”很容易对问题理解不深代码写出隐藏的bug。2. “暴力”是验证优化的基准。当你想到一个更“聪明”的算法时如何验证它的正确性一个可靠的方法就是用暴力解法生成小规模数据的结果与你的新算法结果进行对比对拍。在竞赛中写一个“暴力对拍器”是调试的利器。3. “暴力”中蕴含优化线索。很多高效算法都是从暴力法优化而来的。例如动态规划常常源于暴力递归双指针法可能源于暴力双重循环。分析暴力解法的时间复杂度瓶颈在哪里通常是多层循环就找到了优化的方向。在这道题里我们分析后发现数据范围极小所以用数组代替复杂容器这就是基于暴力分析的优化。4. 不要轻视“暴力”的实用性。在软件开发中并非所有场景都需要微秒级的优化。如果数据量很小比如这道题的手机号只有11位一个清晰易懂的暴力解法的可维护性远胜于一个晦涩难懂但“高效”的奇技淫巧。“过早优化是万恶之源”先把事情做对再考虑做好。回到我们开头提到的那些网络热词“快速幂算法”、“八大排序”、“哈希表”、“单调栈”。这些高级算法和数据结构的价值正是在于解决那些“暴力”解法无法胜任的大规模问题。但学习它们时心中若能时刻与最基础的“暴力”解法对比理解它们为何更快、如何工作你的掌握程度会深刻得多。所以下次再看到“暴力实现”时不必觉得它低级。把它当作探索问题的忠实伙伴理解它改进它超越它。这才是扎实的成长路径。这道关于“出租”的题目租给我们的不仅仅是一串数字索引更是一种值得坚持的编程方法论。
延伸阅读

更多相关文章

2026/10/16 17:57:50

本科人工智能专业课程与考级(证)清单

本文基于本人所在院校首届人工智能专业的真实培养方案,把全部专业课程按内容分成八大类并逐门详细介绍。供同专业或想报考人工智能专业的同学参考。 一、课程分类培养方案中的课程按内容可分成八大类。光看课名容易一头雾水,下面我按自己的理解逐类聊聊&…

2026/10/19 22:48:40

睡眠耳机选购指南:从佩戴舒适度到降噪效果,实测避坑全解析

1. 先搞清楚“睡眠耳机”到底解决什么问题,以及它和普通耳机的区别如果你经常因为环境噪音、伴侣打鼾、或者单纯想听点助眠声音而睡不着,然后去搜“睡眠耳机”,大概率会看到一堆长得像耳塞、宣传能“主动降噪”、“无感佩戴”、“助眠音乐”的…

2026/10/19 23:02:46

卷积不止于2D:掌握1D、2D、3D卷积的适用场景

引言:当“图像思维”成为惯性提起卷积神经网络(CNN),绝大多数人脑海中浮现的第一画面就是“图像识别”——一个方形的卷积核在二维像素网格上滑过,提取边缘、纹理,最终认出猫或狗。这种“2D图像”的思维惯性…

2026/10/21 15:03:50

agent-skills:把智能体能力沉淀为可复用、可编排的技能库

agent-skills:把智能体能干的活,沉淀成一套可复用、可编排的技能库做Agent项目时间长了,你会发现一个挺扎心的规律:单看一个智能体的Demo,跑通并不难,难的是让它稳定地干很多不同类型的活。今天聊的这个项目…

2026/10/21 15:03:50

交通信号灯识别数据集:三种标注格式与82.88% mAP复现

前段时间整理了一个带标注信息的交通信号灯识别数据集,核心能力是识别左转灯、红绿黄灯,实测识别率82.88%,并且同步导出了YOLO、COCO JSON、Pascal VOC XML三种标注格式。这个数字不是拍脑袋报的,是我在自己环境里完整跑训练和验证…

2026/10/21 15:03:50

股票期权适当性考试考点拆解:先建立风险边界再谈开户

简介:面向拟参加上交所股票期权适当性考试的投资者、从业者与备考学员,尤其能让对期权规则不熟悉的读者快速入门;这套题库资料以单选题形式系统梳理期权交易核心考点,适合备考时间紧张、需要反复刷题校验的人群。内容涵盖期权买方…

2026/10/21 15:03:50

IntelliJ IDEA Git高效实战:图形化操作深度指南

1. 这不是Git教程,而是我在IntelliJ IDEA里“摸爬滚打”五年攒下的真功夫你点开这个标题,大概率不是想学Git基础命令——git clone、git commit、git push这些,官网文档写得比谁都清楚。你真正卡住的地方,是当一个分支在IDEA里突然…

2026/10/21 14:58:43

AI证件照制作API全流程:人脸检测、人像分割与背景替换实战

1. 项目背景:为什么证件照不能靠"随手拍 随便裁"解决先说个我自己的经历。前阵子有个朋友着急办证,手头只有一张在咖啡馆拍的侧身自拍,问我能不能帮忙"裁一下"当证件照用。我当时就跟他讲,证件照真不是把脑袋…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/21 0:00:35

交通流仿真实战:IDM跟驰与MOBIL换道模型参数调优与避坑指南

简介:这份资源是面向交通工程学习者与Python开发者的微观交通流仿真项目,核心实现智能驾驶模型(IDM)下的车辆跟驰与换道行为,可用于理解速度、密度与流量之间的动态关系,适合具备Python基础、希望深入交通模…

2026/10/21 0:00:35

揭穿智能体表面顺从:用追踪代码重建任务执行确定性

1. 表面顺从不是智能体的“礼貌”,而是任务执行链路上的隐性断点最近在调试一个跨平台长周期任务调度系统时,我反复遇到一种令人困惑的现象:智能体在用户指令发出后,几乎秒级响应“已收到”“正在处理”“请稍候”,UI上…

2026/10/21 0:00:35

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑