贪心算法实战:删数问题与单调栈优化详解
1. 问题引入从键盘到算法的删数博弈刚接触信息学奥赛的同学大概率会在贪心算法的章节里遇到这道经典题目“删数问题”。题目描述很简单给你一个位数不超过250位的正整数k和一个需要删除的数字个数s要求删除s个数字后剩下的数字按原次序组成一个新的正整数并且这个新数要尽可能小。题目链接对应着《信息学奥赛一本通》的1321题和洛谷的P1106题。我第一次看到这个题目时直觉想法是“删掉最大的s个数字不就行了”。但很快就被样例打脸了。比如数字178543要删掉4位。如果删掉最大的4个数字8,7,5,4得到13。但显然更优的解是删掉7,8,5,4得到13吗不对让我们仔细算算。178543删掉7,8,5,4后剩下1和3是13。但最优解其实是143等等我们需要一个系统的方法。这恰恰是这道题的魅力所在它完美地诠释了“局部最优”与“全局最优”的关系是理解贪心算法思想的绝佳入门案例。它看起来是个字符串处理问题但内核是一个关于“选择”的决策问题。我们不仅要在竞赛中解决它更要理解其背后的决策逻辑这种逻辑在后续处理更复杂的调度、优化问题时依然适用。接下来我将拆解这道题的完整解决思路从暴力搜索的直觉开始逐步优化到高效的贪心单调栈实现并分享我在调试和边界处理上踩过的坑。2. 核心思路拆解为什么不能简单删除最大数字我们先从一个更小的例子开始彻底弄懂问题的核心。设数字为n 14329s 2即删除2个数字。错误思路删最大数字是1,4,3,2,9最大的两个是9和4删除后得到132。手动尝试找最优我们的目标是让剩下的数字序列尽可能小。由于数字顺序不能变高位的数字对数值大小的影响是决定性的。因此核心策略应该是尽可能让高位的数字变小。让我们模拟一个决策过程从左边第一位高位开始看数字是1。我们要删除2个数字目前一个都没删。我们有没有可能通过删除1后面的一些数字让一个比1更小的数字来到第一位呢不可能因为1已经是当前最小的数字了后面是4,3,2,9。所以第一位锁定为1。现在考虑第二位。剩下的数字序列是4329我们还需要删除2个数字因为第一位1被保留了。第二位当前是4。我们看看4后面有没有比4小的数字有3和2。如果我们删除4那么3就会来到第二位。这会让整个数从14xxx变成13xxx显然是更优的。所以我们应该删除4。决策逻辑对于当前正在查看的位置如果它后面的数字比它小那么删除当前这个较大的数字让后面较小的数字“升”上来就能使最终结果更小。删除4后数字变为1329我们已经用了1次删除机会还剩1次。现在序列是1,3,2,9我们接下来看第二位现在是3。第二位是3它后面有比它小的2。删除3让2上来数字变为129。用了第2次删除。得到结果129。我们验证一下所有可能删除(4,9)-132删除(4,3)-129删除(4,2)-139删除(1,4)-329... 显然129是最小的。我们的决策过程找到了最优解。这就是贪心算法的核心每一步我们都只考虑“让当前高位尽可能小”这个局部最优目标。具体操作就是从左到右遍历数字维护一个结果序列。对于当前数字如果结果序列的末尾数字比当前数字大且还有删除次数那么就删除末尾数字因为删除这个大的可以让后面相对小的顶上来使得高位更小。重复这个过程直到不能删除为止。如果遍历完还有删除次数没用完就从序列末尾删除因为此时序列已经是非递减的末尾是最大的。这个操作模式非常像维护一个单调栈——我们希望栈内的数字从底到顶是单调不降的。一旦遇到比栈顶小的数字就弹出删除栈顶直到栈顶不大于新数字或删除次数用完。3. 算法实现详解从伪代码到AC代码理解了单调栈贪心思想后我们来实现它。输入是一个字符串num因为250位远超整数范围和一个整数s。3.1 算法流程步骤化初始化创建一个空栈可以用数组或字符串模拟stk来存放最终结果。remain_to_delete s。遍历输入字符串对于num中的每一个字符digit a.关键循环弹栈当栈不为空且栈顶元素 digit且remain_to_delete 0时 - 弹出栈顶元素相当于删除了一个数字。 -remain_to_delete - 1。 b.入栈将当前digit压入栈中。注意这里有一个细微但至关重要的点。即使当前digit是‘0’只要满足弹栈条件也应该进行弹栈操作。例如num“10023”, s1遍历到第二个‘0’时栈顶是‘1’‘1’ ‘0’且还有删除次数那么弹出‘1’第二个‘0’入栈结果是“0023”处理前导零后是“23”。如果因为digit是‘0’就不弹栈结果会是“1023”这就错了。处理剩余的删除次数遍历完成后如果remain_to_delete 0说明栈中的序列已经是非递减的比如12345此时要使得数最小应该从末尾高位数字已固定删除末尾对高位影响最小删除。直接移除栈末尾的remain_to_delete个字符。处理前导零将栈转换为字符串。删除字符串开头所有的‘0’。处理全零情况如果步骤4的结果是空字符串说明最终结果是0应输出“0”。输出结果。3.2 C 代码实现与逐行解析#include iostream #include string using namespace std; string deleteDigits(string num, int s) { string stk; // 用字符串模拟栈stk的末尾就是栈顶 int remain_to_delete s; for (char digit : num) { // 贪心当栈顶数字比当前数字大且还有删除次数就弹出栈顶删除大的 while (!stk.empty() stk.back() digit remain_to_delete 0) { stk.pop_back(); remain_to_delete--; } stk.push_back(digit); // 当前数字入栈 } // 如果遍历完还有删除次数没用完例如原数字是递增的如12345 // 直接从末尾删除因为此时栈内序列是非递减的末尾最大 if (remain_to_delete 0) { stk.erase(stk.end() - remain_to_delete, stk.end()); } // 处理前导零 size_t nonZeroStart 0; while (nonZeroStart stk.size() stk[nonZeroStart] 0) { nonZeroStart; } string result (nonZeroStart stk.size()) ? 0 : stk.substr(nonZeroStart); return result; } int main() { string k; int s; cin k s; cout deleteDigits(k, s) endl; return 0; }代码关键点解析while (!stk.empty() stk.back() digit remain_to_delete 0)这是贪心的核心。三个条件缺一不可栈不空有东西可删、栈顶比当前大删除能使高位变小、还有删除额度。stk.erase(stk.end() - remain_to_delete, stk.end())string的erase方法用于删除剩余字符。stk.end()是指向末尾的迭代器。前导零处理使用while循环找到第一个非零字符的位置nonZeroStart。如果nonZeroStart等于字符串长度说明全是零输出“0”。3.3 一个完整的演算示例以num “178543”, s 4为例我们走一遍算法当前digit栈stk (栈底-栈顶)remain_to_delete操作说明初始[]4‘1’[1]4栈空直接入栈‘7’[1,7]4栈顶17不弹栈直接入栈‘8’[1,7,8]4栈顶78入栈‘5’[1,7,5]3栈顶85弹栈8remain3。新栈顶75弹栈7remain2。新栈顶15停止弹栈5入栈。‘4’[1,5,4]1栈顶54弹栈5remain1。新栈顶14停止4入栈。‘3’[1,4,3]0栈顶43但remain0无法弹栈。3入栈。遍历结束[1,4,3]0剩余删除次数为0无需操作。处理前导零“143”无前导零。最终结果为“143”。你可以验证这确实是最小值。4. 边界条件与常见“坑点”实录这道题思路清晰后代码不难但边界情况非常考验细节。以下是几个极易出错的点我都曾在这里栽过跟头。4.1 坑点一前导零的处理时机与逻辑这是最常见的错误。必须在删除操作全部完成后最后一步处理前导零。绝对不能边删除边处理或者在栈操作中忽略‘0’。错误做法在入栈前判断如果digit是‘0’且栈为空就不入栈以为能跳过前导零。这会导致删除次数计算错误。例num”10023”, s1。正确结果是”0023”-”23”。错误逻辑读第一个‘1’栈空入栈。读第二个‘0’栈非空但digit是‘0’如果因为栈空时不入栈‘0’的逻辑这里会忽略。实际上我们应该用贪心规则栈顶‘1’ ‘0’且remain1所以弹出‘1’然后‘0’入栈。这样栈变成了[0]。后续操作得到”0023”。正确做法如前文代码所示将所有数字包括‘0’一视同仁地参与单调栈的贪心比较。最后再将结果字符串前面的‘0’全部去掉。4.2 坑点二删除次数用不完的情况如果原数字序列本身就是非递减的如”12345”那么遍历过程中的while循环一次都不会执行。如果s2遍历后栈为”12345”remain_to_delete2。错误做法不处理直接输出”12345”。正确做法算法步骤3直接从字符串末尾删除剩余次数的字符。”12345”删除末尾2位得到”123”。因为在高位已固定的情况下删除末尾最大的数字能使剩下的数最小。4.3 坑点三结果为全零的判断处理完前导零后字符串可能为空。例如num”1000”, s1。贪心过程‘1’入栈遇到第一个‘0’弹出‘1’‘0’入栈。后面‘0’,‘0’依次入栈因为栈顶‘0’不大于新‘0’。栈为”000”。删除剩余次数remain_to_delete0不操作。处理前导零删除所有‘0’结果字符串为空。此时必须输出”0”而不是空字符串。否则会WAWrong Answer。4.4 坑点四字符串与数字的混淆题目明确说明位数可达250位这远远超出了任何标准整数类型long long约19位的范围。因此必须用字符串string来接收和存储输入的数字。所有的比较、删除操作都在字符串上进行。比较字符‘5’和‘2’时比较的是它们的ASCII码对于数字字符来说是等价的但心里要清楚我们是在处理字符。5. 算法正确性证明与贪心策略的理解为什么这种“见大就删”的贪心策略能得到全局最优解我们可以这样理解决策的高位优先原则对于一个数字其大小首先由最高位决定。因此我们的首要目标是让最高位最小。在删除次数固定的情况下我们应该把删除的机会“用在刀刃上”即优先用来降低高位的数字。单调栈的局部最优性我们从左到右扫描。假设当前扫描到位置i栈内保存了前i-1个数字中在已执行了若干次删除后所能形成的、且满足“栈内单调不降”的最优前缀序列。现在考虑第i个数字num[i]。如果num[i]大于等于栈顶直接入栈保持了栈的单调性且没有浪费删除机会去删除一个可能使高位变大的数字。如果num[i]小于栈顶说明栈顶元素是一个“高位上的大数”。删除它如果还有机会让更小的num[i]占据这个位置对于这个特定的高位位置来说是立刻得到改善的。而且这个决策是“安全”的因为我们只删除了一个已经存在于结果中的、相对较大的数字换上一个更小的对于已经固定的更前的高位没有影响。无后效性这个决策是“向前看”的。删除栈顶一个已确定的高位数字不会影响后续的决策因为后续决策只关心剩下的数字序列和剩余的删除次数。它不会导致未来出现一个本该被删除的更大数字因为这次删除而“逃过一劫”。因此每一步都采取“当栈顶大于新数字时则弹出栈顶”的局部最优策略最终累积起来就是全局最优解。这个证明虽然不形式化但非常有助于我们直观把握贪心算法的精髓。6. 性能分析与拓展思考时间复杂度每个数字最多入栈一次、出栈一次所以时间复杂度是O(n)其中 n 是输入数字的位数≤250。这对于题目限制来说是绰绰有余的。空间复杂度主要使用了模拟栈的字符串空间复杂度为O(n)。拓展思考如果要求删除后数字最大怎么办只需将贪心策略反向维护一个单调不增的栈。当栈顶小于当前数字且还有删除次数时弹出栈顶。其余逻辑不变。如果数字中有前导零输入时就有我们的算法已经包含了处理逻辑因为输入是字符串开头的‘0’也会被当作普通字符处理。例如”00123”, s1算法会正确输出”0123”-”123”。更复杂的变种如果删除规则不是指定删除个数而是指定删除某些特定数字或者要求删除后数字是某个数的倍数等那就需要用到动态规划等其他算法了。这道“删数问题”是贪心算法的一个经典教学案例。它告诉我们面对一个优化问题时先分析影响结果的关键因素这里是高位数字然后设计一种每一步都朝着优化该因素方向前进的策略单调栈维护最小高位并小心验证边界条件前导零、剩余删除次数往往就能得到一个简洁高效的解法。在竞赛中遇到类似“构造最小/最大序列”的问题时不妨想想是否能用这种“单调栈贪心”的思路来解决。

相关新闻

GitHub中文插件:3分钟让你的GitHub界面告别英文困扰

GitHub中文插件:3分钟让你的GitHub界面告别英文困扰

GitHub中文插件:3分钟让你的GitHub界面告别英文困扰 【免费下载链接】github-chinese GitHub 汉化插件,GitHub 中文化界面。 (GitHub Translation To Chinese) 项目地址: https://gitcode.com/gh_mirrors/gi/github-chinese 你是否曾经因为GitHub…

2026/8/8 8:34:29 阅读更多 →
Elasticsearch ES|QL 将全文搜索带到你从未建立索引的数据中

Elasticsearch ES|QL 将全文搜索带到你从未建立索引的数据中

作者:来自 Elastic Kevin Corcoran 及 Ioana Tagirta MATCH 和 TO_TEXT 将全文搜索能力带到你从未建立索引的数据中。在 ES|QL 中,你可以对计算列、未映射字段以及联邦数据源执行全文搜索。 ES|QL MATCH 现在可以对你从未建立索引的数据执行全文搜索。无…

2026/8/8 8:33:29 阅读更多 →
电饭锅开关失灵?一文搞懂磁钢限温器原理与机械故障修复

电饭锅开关失灵?一文搞懂磁钢限温器原理与机械故障修复

你有没有遇到过这样的场景:早上急着上班,想煮个粥,结果电饭锅的开关死活按不下去;或者晚上回家想煮饭,按键按了没反应,指示灯也不亮。那一刻,你可能会觉得,这个陪伴多年的“厨房伙伴…

2026/8/8 8:33:29 阅读更多 →

最新新闻

深度解析qmqtt:Qt MQTT客户端架构设计与性能优化指南

深度解析qmqtt:Qt MQTT客户端架构设计与性能优化指南

深度解析qmqtt:Qt MQTT客户端架构设计与性能优化指南 【免费下载链接】qmqtt MQTT client for Qt 项目地址: https://gitcode.com/gh_mirrors/qm/qmqtt qmqtt是一个专门为Qt框架设计的轻量级MQTT客户端库,为Qt开发者提供了完整的MQTT协议实现方案…

2026/8/8 15:18:01 阅读更多 →
游戏PAK文件解析实战:从结构拆解到Python工具实现

游戏PAK文件解析实战:从结构拆解到Python工具实现

大家好,我是专注于技术实战分享的博主。最近在分析一些移动端游戏时,经常会遇到 .pak 格式的资源文件,这类文件在 Unity 游戏、虚幻引擎项目以及像《地铁跑酷》这类热门手游中非常常见。游戏开发者通过将图片、音频、配置表等资源打包成 .…

2026/8/8 15:18:01 阅读更多 →
审小匠 vs 手工 Excel:合并 TB 与附注、内部往来抵消评测

审小匠 vs 手工 Excel:合并 TB 与附注、内部往来抵消评测

审小匠 vs 手工 Excel:合并 TB 与附注、内部往来抵消评测 一、背景痛点:合并底稿的活儿,卡在"把散数据拼成一张表" 做过集团合并的人都清楚,合并报表本身的会计逻辑并不神秘,难的是前置的数据工程&#xff1…

2026/8/8 15:18:01 阅读更多 →
深入解析网站建设前台与后台最新技术:从用户体验到数据安全的全面升级指南

深入解析网站建设前台与后台最新技术:从用户体验到数据安全的全面升级指南

在这个数字化浪潮席卷全球的今天,网站已经不再仅仅是一个展示企业形象的“电子名片”,它更是 businesses 与用户交互的核心枢纽,是数据流转的动脉,是品牌形象的直接载体。作为一名在行业里摸爬滚打多年的从业者,我见过太多曾经叱咤风云的网站因为技术滞后而被时代淘汰,也…

2026/8/8 15:18:00 阅读更多 →
如何快速安装Realtek r8125 DKMS驱动:轻松开启2.5GbE高速网络体验的终极指南 [特殊字符]

如何快速安装Realtek r8125 DKMS驱动:轻松开启2.5GbE高速网络体验的终极指南 [特殊字符]

如何快速安装Realtek r8125 DKMS驱动:轻松开启2.5GbE高速网络体验的终极指南 🚀 【免费下载链接】realtek-r8125-dkms A DKMS package for easy use of Realtek r8125 driver, which supports 2.5 GbE. 项目地址: https://gitcode.com/gh_mirrors/re/r…

2026/8/8 15:18:00 阅读更多 →
LLM Agent工作流实战:从工具设计到生产部署的架构与避坑指南

LLM Agent工作流实战:从工具设计到生产部署的架构与避坑指南

1. 从“聊天机器人”到“工作流执行者”:LLM Agent的范式转变如果你最近还在把大语言模型(LLM)当作一个更聪明的聊天机器人,或者一个高级的文本生成器,那可能已经有点落伍了。一个更激动人心的趋势正在发生&#xff1a…

2026/8/8 15:17:00 阅读更多 →

日新闻

AI多智能体时代来临,读懂MCP与A2A架构,抢占企业数字化新风口

AI多智能体时代来临,读懂MCP与A2A架构,抢占企业数字化新风口

当下AI应用飞速普及,无数企业下场搭建智能体系统,可落地阶段难题接踵而至:上下文无限堆积频繁爆栈、AI工具调用准确率低下、Token成本居高不下、企业数据权限混乱暗藏安全隐患……很多团队卡在架构搭建环节,空有前沿技术概念&…

2026/8/8 0:00:07 阅读更多 →
PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码

PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码

PHP二维码生成终极指南:用chillerlan/php-qrcode打造专业级二维码 【免费下载链接】php-qrcode A PHP QR Code generator and reader with a user-friendly API. 项目地址: https://gitcode.com/gh_mirrors/ph/php-qrcode 在当今数字时代,二维码已…

2026/8/8 0:00:08 阅读更多 →
UniApp微信小程序隐私保护组件开发:从原理到实战

UniApp微信小程序隐私保护组件开发:从原理到实战

1. 项目缘起:为什么我们需要一个隐私保护通用组件?最近在维护一个基于uniapp开发的微信小程序矩阵时,我遇到了一个非常棘手的问题。随着平台对用户隐私保护的要求越来越严格,几乎每一个新版本发布,或者在某些特定机型&…

2026/8/8 0:00:08 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/6 22:02:27 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/8 8:58:26 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/7 23:24:08 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/7 17:02:37 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/7 23:54:54 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/7 17:02:36 阅读更多 →