402.移掉K位数字
题目描述
给你一个以字符串表示的非负整数 num
和一个整数 k
,移除这个数中的 k
位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
示例 1:
1
2
3
输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3, 和 2 后,剩下的数字是 "1219",这是可能的最小值。
示例 2:
1
2
3
输入:num = "10200", k = 1
输出:"200"
解释:移除掉第一位的 1 后,剩下的数字是 "200",这是可能的最小值。
示例 3:
1
2
3
输入:num = "10", k = 2
输出:"0"
解释:从原数字移除所有的数字,剩余为空就是 0。
解题思路
这道题是一个典型的单调栈应用。主要思路如下:
- 从左到右遍历数字,使用栈维护一个单调递增的序列
- 当遇到一个数字比栈顶元素小时,且还有可以移除的次数(k>0)时,移除栈顶元素
- 重复步骤2直到不能再移除或者栈为空
- 处理特殊情况(前导零、k仍大于0等)
简洁优化版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
func removeKdigits(_ num: String, _ k: Int) -> String {
// 特殊情况处理
guard k < num.count else { return "0" }
guard k > 0 else { return num }
// 初始化栈和计数器
var stack = [Character]()
var remain = k
for char in num {
// 对于每个新数字,我们都要和栈顶元素比较
while let last = stack.last, // 获取栈顶元素
last > char, // 栈顶元素比当前数字大
remain > 0 { // 还有可以删除的次数
stack.removeLast() // 移除栈顶元素
remain -= 1 // 可删除次数减1
}
stack.append(char) // 将当前数字入栈
}
// 处理未使用完的k
stack.removeLast(remain)
// 移除前导零并构建结果
let result = String(stack).drop { $0 == "0" }
return result.isEmpty ? "0" : String(result)
}
}
代码说明
- 特殊情况处理:
1 2
guard k < num.count else { return "0" } guard k > 0 else { return num }
- 如果k大于等于字符串长度,返回”0”
- 如果k为0,直接返回原字符串
- 栈的初始化:
1 2
var stack = [Character]() var remain = k
- 使用Character数组作为栈
- remain记录还需要删除的数字个数
- 核心逻辑:
1 2 3 4 5 6
while let last = stack.last, last > char, remain > 0 { stack.removeLast() remain -= 1 }
- 使用可选绑定安全地获取栈顶元素
- 多条件判断更加清晰
- 当栈顶元素大于当前数字时移除
- 后处理:
1 2 3
stack.removeLast(remain) let result = String(stack).drop { $0 == "0" } return result.isEmpty ? "0" : String(result)
- 一次性移除剩余需要删除的数字
- 使用drop优雅地处理前导零
- 使用三元运算符处理空结果
优化说明
- 代码简化:
- 使用guard语句处理特殊情况
- 使用可选绑定和多条件判断
- 使用Swift的高阶函数特性
- 性能优化:
- 减少类型转换
- 使用Character而不是Int存储
- 使用removeLast(n)批量删除
- 安全性提升:
- 使用可选绑定避免强制解包
- 处理所有边界情况
- 确保返回值的正确性
这个版本的代码更加简洁易读,同时保持了良好的性能和安全性。通过详细的注释,使得代码的意图和实现方式更加清晰。
代码分析
- 边界条件处理:
- 如果k大于等于字符串长度,直接返回”0”
- 最后如果结果为空,返回”0”
- 核心逻辑:
- 使用栈来维护结果
- 遍历每个数字时,与栈顶元素比较
- 当前数字小于栈顶元素且还能移除数字时,移除栈顶元素
- 后处理:
- 处理剩余的k(从末尾移除)
- 移除前导零
- 处理空栈情况
复杂度分析
- 时间复杂度:O(n),其中 n 是数字字符串的长度。虽然有嵌套的循环,但每个字符最多被压入和弹出栈一次。
- 空间复杂度:O(n),需要一个栈来存储中间结果。
算法优化思考
- 提前返回:
- 当k等于字符串长度时可以提前返回
- 当字符串只包含一个字符时可以简化处理
- 内存优化:
- 可以使用字符数组代替栈,减少内存分配
- 可以直接在原字符串上操作(如果允许修改输入)
- 性能优化:
- 可以在移除前导零的同时构建结果字符串
- 可以使用StringBuilder类型的数据结构来提高字符串操作效率
总结
这道题展示了单调栈的经典应用,通过维护栈的单调递增特性来找到最优解。关键点在于:
- 理解单调栈的思想:维护递增序列,较大数字会被弹出
- 使用栈来维护单调性
- 处理好各种边界情况
- 注意前导零的处理
掌握这道题的解法对理解单调栈的应用很有帮助。
本文由作者按照 CC BY 4.0 进行授权