Article / 文章

LeetCode 第402题:移掉K位数字

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

📖 文章摘要

本文详细解析LeetCode第402题“移掉K位数字”,这是一道贪心算法和单调栈的经典问题。文章提供了完整的解题思路,包含C#、Python、C++三种语言实现,配有详细的分析和性能对比。适合想要学习贪心算法和单调栈的读者。

核心知识点: 贪心算法、单调栈、字符串处理
难度等级: 中等
推荐人群: 对贪心算法和单调栈感兴趣的进阶学习者

题目描述

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

示例

示例 1:

输入:num = “1432219”, k = 3 输出:“1219” 解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。

示例 2:

输入:num = “10200”, k = 1 输出:“200” 解释:移掉首位的 1 剩下的数字为 200。注意输出不能有任何前导零。

示例 3:

输入:num = “10”, k = 2 输出:“0” 解释:从原数字移除所有的数字,剩余为空就是 0。

提示

  • 1 <= num.length <= 105
  • num 仅由若干位数字(0-9)组成
  • 除了 0 本身之外,num 不含任何前导零

解题思路

这道题可以使用贪心算法结合单调栈来解决。核心思路如下:

  1. 贪心策略

    • 从左到右遍历数字
    • 如果当前数字小于栈顶数字,且还可以删除数字(k>0)
    • 则删除栈顶数字(贪心地让高位数字尽可能小)
  2. 单调栈维护

    • 使用栈来维护当前保留的数字
    • 保持栈中数字单调不降
    • 最终栈中的数字就是结果
  3. 特殊情况处理

    • 删除前导零
    • 处理空串情况
    • 处理k仍有剩余的情况

图解思路

算法步骤分析表

步骤 操作 状态 说明
初始状态 - 栈空 准备处理输入字符串
遍历字符 比较当前字符与栈顶 维护单调栈 如果当前字符更小且k>0,弹出栈顶
处理剩余k 从末尾删除 完成删除操作 如果k仍大于0,从末尾删除k个字符
处理前导零 删除前导零 得到最终结果 确保结果不含前导零且非空

状态/情况分析表

情况 输入 输出 说明
基本情况 “1432219”, k=3 “1219” 删除高位大数字
前导零 “10200”, k=1 “200” 需要处理前导零
全部删除 “10”, k=2 “0” 返回“0”而不是空串

代码实现

C# 实现

public class Solution {
    public string RemoveKdigits(string num, int k) {
        if (string.IsNullOrEmpty(num) || k >= num.Length)
            return "0";
            
        var stack = new Stack<char>();
        
        // 遍历每个数字
        foreach (char c in num) {
            // 当前数字小于栈顶数字,且还可以删除数字时,删除栈顶
            while (k > 0 && stack.Count > 0 && stack.Peek() > c) {
                stack.Pop();
                k--;
            }
            stack.Push(c);
        }
        
        // 如果k还没有用完,从末尾删除
        while (k > 0 && stack.Count > 0) {
            stack.Pop();
            k--;
        }
        
        // 构建结果字符串,注意去除前导零
        var result = new string(stack.Reverse().ToArray());
        result = result.TrimStart('0');
        
        return string.IsNullOrEmpty(result) ? "0" : result;
    }
}

Python 实现

class Solution:
    def removeKdigits(self, num: str, k: int) -> str:
        if not num or k >= len(num):
            return "0"
            
        stack = []
        
        # 遍历每个数字
        for digit in num:
            # 当前数字小于栈顶数字,且还可以删除数字时,删除栈顶
            while k > 0 and stack and stack[-1] > digit:
                stack.pop()
                k -= 1
            stack.append(digit)
        
        # 如果k还没有用完,从末尾删除
        while k > 0 and stack:
            stack.pop()
            k -= 1
        
        # 构建结果字符串,去除前导零
        result = ''.join(stack).lstrip('0')
        
        return result if result else "0"

C++ 实现

class Solution {
public:
    string removeKdigits(string num, int k) {
        if (num.empty() || k >= num.length())
            return "0";
            
        vector<char> stack;
        
        // 遍历每个数字
        for (char c : num) {
            // 当前数字小于栈顶数字,且还可以删除数字时,删除栈顶
            while (k > 0 && !stack.empty() && stack.back() > c) {
                stack.pop_back();
                k--;
            }
            stack.push_back(c);
        }
        
        // 如果k还没有用完,从末尾删除
        while (k > 0 && !stack.empty()) {
            stack.pop_back();
            k--;
        }
        
        // 构建结果字符串
        string result(stack.begin(), stack.end());
        
        // 去除前导零
        result.erase(0, result.find_first_not_of('0'));
        
        return result.empty() ? "0" : result;
    }
};

执行结果

C# 实现

  • 执行用时:84 ms
  • 内存消耗:38.2 MB

Python 实现

  • 执行用时:40 ms
  • 内存消耗:15.8 MB

C++ 实现

  • 执行用时:0 ms
  • 内存消耗:7.1 MB

性能对比

语言 执行用时 内存消耗 特点
C# 84 ms 38.2 MB 代码简洁,但性能较差
Python 40 ms 15.8 MB 实现简单,性能中等
C++ 0 ms 7.1 MB 性能最优,内存占用最小

代码亮点

  1. 🎯 使用单调栈实现贪心策略
  2. 💡 高效处理字符串和前导零
  3. 🔍 优雅处理边界情况
  4. 🎨 代码结构清晰,易于理解和维护

常见错误分析

  1. 🚫 忘记处理前导零的情况
  2. 🚫 没有正确处理k值用完后的情况
  3. 🚫 栈操作顺序错误
  4. 🚫 返回空字符串而不是“0”

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
暴力法 O(k×n) O(n) 思路简单 性能很差
贪心+单调栈 O(n) O(n) 性能最优 需要理解单调栈
动态规划 O(k×n) O(k×n) 通用性好 空间复杂度高

相关题目

📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第402题。

💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!