洛谷P1106题解:贪心策略与单调栈实现删除k位最小数

字符串处理与贪心算法的结合常常考验我们对问题本质的理解。本文将以洛谷P1106题为例,详细介绍如何通过贪心算法删除k位数字使剩余数字最小,帮助新手掌握单调栈和贪心算法的应用技巧。
一、问题解析
题目要求给定一个字符串表示的数字和一个整数k,删除其中k位数字,使得剩余数字形成的数最小。核心挑战在于:
必须删除恰好k位数字
数字顺序不能改变
需要去除结果中的前导零
处理结果为空的情况
贪心策略核心思想:
局部最优导致全局最优:尽可能让高位的数字变小
单调栈处理:维护一个单调不降的序列
前导零处理:删除结果中开头的所有零
二、完整代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 删除k位数字的函数
string removeKDigits(string num, int k) {
// 使用vector模拟栈结构
vector<char> stack;
// 遍历输入数字的每一位
for (char digit : num) {
// 贪心策略:当栈非空、还有删除次数且栈顶数字大于当前数字时
while (!stack.empty() && k > 0 && stack.back() > digit) {
stack.pop_back(); // 删除栈顶数字(删除高位较大的数字)
k--; // 减少剩余删除次数
}
// 将当前数字加入栈中
stack.push_back(digit);
}
// 如果还有k需要删除,从末尾删除(此时栈中已是单调不减序列)
while (k-- > 0) {
stack.pop_back(); // 删除末尾较大的数字
}
// 构建结果字符串并去除前导零
string result; // 存储最终结果
bool leadingZero = true; // 标记是否处于前导零状态
// 遍历栈中每个数字
for (char digit : stack) {
// 跳过前导零
if (leadingZero && digit == '0') continue;
// 遇到非零数字或已有非零数字,结束前导零状态
leadingZero = false;
result += digit; // 将数字添加到结果
}
// 处理结果为空的情况(如删除所有数字)
return result.empty() ? "0" : result;
}
int main() {
string num; // 存储输入的数字字符串
int k; // 要删除的数字位数
cin >> num >> k; // 读取输入
// 调用函数并输出结果
cout << removeKDigits(num, k) << endl;
return 0;
}三、算法解析
1. 贪心删除策略
算法的核心在于维护一个单调不降的序列:
遍历数字字符串的每个字符
当满足三个条件时弹出栈顶元素:
栈非空
还有剩余删除次数(k > 0)
栈顶数字 > 当前数字
将当前数字压入栈中
为什么这样有效:
高位数字的权重更大,优先优化高位
当高位数字大于低位时,删除高位更有利于数值减小
示例:
num="14329", k=3:删除4(1<4>3 → 删除4)
删除3(1<3>2 → 删除3)
删除9(最后结果:12)
2. 后续处理
剩余删除处理:如果遍历结束后仍有剩余的k,删除末尾k个数字
原因:此时栈中已是单调不降序列,末尾数字最大
前导零处理:跳过结果开头的所有零
空结果处理:当结果为空时返回"0"
四、常见错误
未处理前导零:可能导致
"00234"输出错误忽略空结果:删除所有数字后应返回"0"
错误处理剩余k:遍历后剩余的k必须从末尾删除
顺序变更:改变了数字的相对位置
栈使用不当:使用vector模拟栈可高效插入删除
原创内容 转载请注明出处
