1. 问题背景与核心需求
这道题目来自信息学奥赛经典教材《信息学奥赛一本通》的第1321题,属于贪心算法类问题的典型代表。题目要求在一个给定的数字序列中,删除指定数量的数字,使得剩下的数字组成的新数最小。比如给定数字"175438",要求删除3个数字,最优解是删除7、5、4得到"138"。
这类问题在实际中有广泛的应用场景:
- 金融领域的票据号码优化
- 工业生产中的序列号压缩
- 数据处理时的关键特征提取
2. 算法设计思路解析
2.1 贪心算法选择依据
采用贪心算法是因为这个问题具有最优子结构性质:
- 局部最优解能导致全局最优解
- 无后效性:当前选择不影响后续选择
具体来说,要确保最终数字最小,应该让高位的数字尽可能小。这就决定了我们需要从左到右遍历,在允许删除的范围内,尽可能删除较大的数字。
2.2 关键数据结构选择
使用栈(Stack)是最优选择,原因如下:
- 需要频繁比较当前元素与栈顶元素
- 需要支持快速回溯(pop操作)
- 时间复杂度可以优化到O(n)
相比数组实现,栈的空间局部性更好,操作更高效。
3. 详细实现步骤
3.1 算法流程分解
- 初始化空栈和删除计数器
- 遍历数字字符串:
- 当栈非空且当前数字<栈顶数字且还有删除额度时:
- 弹出栈顶元素
- 删除计数+1
- 将当前数字压入栈
- 当栈非空且当前数字<栈顶数字且还有删除额度时:
- 处理剩余删除额度(从末尾删除)
- 去除前导零
- 输出结果
3.2 边界条件处理
需要特别注意的特殊情况:
- 全零输入
- 要求删除所有数字
- 存在连续相同数字
- 删除后结果为空
4. 代码实现与注释
def removeKDigits(num, k): stack = [] remain = len(num) - k for digit in num: while k and stack and stack[-1] > digit: stack.pop() k -= 1 stack.append(digit) # 处理剩余需要删除的情况 result = stack[:remain] # 去除前导零 return ''.join(result).lstrip('0') or '0'关键点说明:
remain计算确保最终保留正确数量的数字lstrip('0')处理前导零or '0'确保空结果返回0
5. 复杂度分析与优化
5.1 时间复杂度
最优情况:O(n) 最坏情况:O(n) 平均情况:O(n)
每个数字最多入栈、出栈一次,因此是线性复杂度。
5.2 空间复杂度
O(n) 用于存储栈
可以优化到O(1)如果直接在原字符串上操作,但会牺牲可读性。
6. 常见错误与调试技巧
6.1 典型错误类型
- 未处理前导零
- 删除数量计算错误
- 边界条件遗漏
- 贪心策略实现不完整
6.2 调试方法
建议测试用例:
- "10200" 删除1 → "200"
- "12345" 删除3 → "12"
- "10001" 删除4 → "0"
- "11111" 删除2 → "111"
7. 算法扩展与变种
7.1 相关变种问题
- 求删除后的最大数
- 带权重的删数问题
- 限制删除位置的变种
- 多次查询的在线处理
7.2 实际应用优化
在真实场景中可能需要考虑:
- 超大数处理(分块算法)
- 流式数据处理
- 分布式实现
8. 竞赛技巧与注意事项
- 先写暴力解法验证思路
- 注意输入输出格式要求
- 预留足够时间处理边界条件
- 使用自定义测试用例验证
在竞赛中遇到类似题目时,建议先花2-3分钟在草稿纸上模拟小例子,确保完全理解题意和算法逻辑后再开始编码。