LeetCode 402 - 移掉 K 位数字


文章目录

摘要

在很多前端或后端的业务逻辑中,我们经常要处理数字的"裁剪"问题,比如在账单明细里自动保留最小金额组合、或在数据压缩时尽量保留较小值。LeetCode 第 402 题《移掉 K 位数字》(Remove K Digits)就是一个非常贴近这种逻辑的算法题。

题目的核心是:给定一个非负整数(以字符串形式表示),从中移除 k 个数字,使得剩下的数字最小化。

看似简单,但要保证最小值且保持相对顺序 ,就必须巧妙地使用单调栈(Monotonic Stack)

描述

题目要求如下:

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

例如:

txt 复制代码
输入:num = "1432219", k = 3
输出:"1219"

解释:

我们删掉 4、3、2,得到 1219,这是最小的组合。

再看另一个例子:

txt 复制代码
输入:num = "10200", k = 1
输出:"200"

因为去掉开头的 1 后,最小结果是 "200",同时要注意不能有前导零。

最后一种极端情况:

txt 复制代码
输入:num = "10", k = 2
输出:"0"

删光了,只能输出 "0"。

题解答案

思路其实很自然:

  1. 我们想要一个尽可能小的数字。
  2. 所以,每当我们看到一个"比前一个更小"的数字,就说明前面的数字没必要留着,可以删掉让整体更小。
  3. 这正是典型的 "单调递增栈" 思想。
    我们用一个栈来存放数字,如果当前数字比栈顶小,就把栈顶弹出(删掉)直到不满足条件或删够了 k 个。
  4. 最后,如果还没删够,就从尾部继续删。
  5. 结果去掉前导零,若为空返回 "0"。

题解代码分析

下面是完整的 Swift 实现,可以直接在 Xcode 或 Playground 中运行:

swift 复制代码
import Foundation

class Solution {
    func removeKdigits(_ num: String, _ k: Int) -> String {
        var stack: [Character] = []
        var removeCount = k

        for digit in num {
            // 每当当前数字比栈顶小,就弹出栈顶(相当于删除)
            while removeCount > 0, let last = stack.last, last > digit {
                stack.removeLast()
                removeCount -= 1
            }
            stack.append(digit)
        }

        // 如果还没删够,就从尾部删除
        while removeCount > 0, !stack.isEmpty {
            stack.removeLast()
            removeCount -= 1
        }

        // 构建最终字符串并去掉前导零
        var result = String(stack).drop(while: { $0 == "0" })
        return result.isEmpty ? "0" : String(result)
    }
}

// MARK: - 可运行示例
let solution = Solution()
print(solution.removeKdigits("1432219", 3)) // 输出: 1219
print(solution.removeKdigits("10200", 1))   // 输出: 200
print(solution.removeKdigits("10", 2))      // 输出: 0

代码逻辑逐步拆解:

  • 栈的核心思想

    栈用来保存当前有效的数字序列。每个新数字进来之前,都会看一眼栈顶是否"更大",如果更大,就弹出。

    比如处理 "1432219"

    • 1 → [1]
    • 4 → [1, 4]
    • 3 → 3 < 4 → 弹出 4 → [1, 3]
    • 2 → 2 < 3 → 弹出 3 → [1, 2]
    • 2 → [1, 2, 2]
    • 1 → 1 < 2 → 弹出两个 2 → [1, 1]
    • 9 → [1, 1, 9]
      最终得到 1219
  • 删除次数控制

    我们用 removeCount 计数,每次弹出一个数字就减 1。

    如果循环结束后还没删够,就直接从尾巴删,保持最小化。

  • 前导零处理

    比如 "10200" → 删除 1 后变成 "0200",要去掉前导零,得到 "200"。

示例测试及结果

我们来跑几组不同的示例看看结果是否符合预期。

swift 复制代码
let solution = Solution()
print(solution.removeKdigits("1432219", 3)) // 1219
print(solution.removeKdigits("10200", 1))   // 200
print(solution.removeKdigits("10", 2))      // 0
print(solution.removeKdigits("1234567890", 9)) // 0
print(solution.removeKdigits("112", 1))     // 11

输出结果如下:

txt 复制代码
1219
200
0
0
11

非常符合预期。

时间复杂度

  • 主循环中每个数字最多入栈、出栈一次,
    所以整体复杂度是 O(n),其中 n 是字符串长度。

这种复杂度对于 num.length 高达 10^5 的情况也能轻松应对。

空间复杂度

  • 我们用了一个栈来存储数字,最坏情况下保存所有字符,
    所以空间复杂度是 O(n)

总结

这道题看似是个字符串问题,其实本质是个"贪心 + 单调栈"的组合问题。

  • 贪心保证我们每一步都尽可能让左边数字小;
  • 栈保证我们能方便地删除不合适的数字;
  • 去除前导零的细节让结果符合格式要求。

在实际开发中,这种思想也很常见,比如:

  • 表单验证时自动修剪冗余字符
  • 财务系统中压缩冗余金额字段
  • UI 中动态展示最小数值状态

这道题不仅是算法题,更像是"如何在一堆数里精简出最优解"的典型案例。

相关推荐
永远睡不够的入5 小时前
类和对象(上)
开发语言·c++·算法
想进个大厂5 小时前
代码随想录day31 贪心05
数据结构·算法·leetcode
yyy(十一月限定版)5 小时前
寒假集训1——暴力和枚举
数据结构·算法
橘颂TA5 小时前
【剑斩OFFER】算法的暴力美学——力扣 207 题:课程表
数据结构·c++·算法·leetcode·职场和发展
谁刺我心5 小时前
【蓝桥杯刷题环境】VScode插件算法刷题Competitive Companion
职场和发展·蓝桥杯
孞㐑¥5 小时前
算法—链表
开发语言·c++·经验分享·笔记·算法
学嵌入式的小杨同学5 小时前
【Linux 封神之路】文件操作 + 时间编程实战:从缓冲区到时间格式化全解析
linux·c语言·开发语言·前端·数据库·算法·ux
xqqxqxxq5 小时前
结构体(Java 类)实战题解笔记(持续更新)
java·笔记·算法
会开花的二叉树5 小时前
高性能定时器:时间轮算法的工程实践
算法
大江东去浪淘尽千古风流人物5 小时前
【LingBot-Depth】Masked Depth Modeling for Spatial Perception
人工智能·算法·机器学习·概率论