【C++算法打怪专栏】LeetCode 402. 移掉 K 位数字(单调栈 + 贪心)
一、 问题描述
给你一个以字符串表示的非负整数 num 和一个整数 k,移除这个数中的 k 位数字,使得剩下的数字最小。请你返回修剪后的数字。
- 示例 1 :
- 输入:
num = "1432219",k = 3 - 输出:
"1219" - 解释:移除掉 3 个数字 4, 3, 和 2 形成一个新的最小数字 1219。
- 输入:
- 示例 2 :
- 输入:
num = "10200",k = 1 - 输出:
"200" - 解释:移掉首位的 1 剩下的数字为
"0200",转换为数字后即为"200"。
- 输入:
二、 核心解题思路:贪心算法 + 单调栈
- 贪心策略 :
从左到右扫描字符串,如果当前的数字比前一个数字小(即出现降序),为了让整体数值变小,应该优先删掉前面的大数字。 - 单调栈维护 :
使用一个栈来维护一个单调递增的数字序列。遍历过程中:- 当栈不为空、栈顶元素大于当前字符、且还有删除次数(
k > 0)时,弹出栈顶元素,并将k减 1。 - 将当前字符压入栈中。
- 当栈不为空、栈顶元素大于当前字符、且还有删除次数(
- 收尾与前导零处理 :
- 如果遍历结束后
k仍然大于 0(例如原字符串本身单调递增),则从栈顶继续弹出剩余的次数。 - 将栈中收集的字符反转并拼成字符串,同时剔除多余的前导零。
- 若最终结果为空,则返回
"0"。
- 如果遍历结束后
三、 C++ 完整源代码
cpp
#include <string>
#include <stack>
#include <algorithm>
using namespace std;
class Solution {
public:
string removeKdigits(string num, int k) {
int n = num.size();
stack<char> st;
// 1. 遍历字符串,利用单调栈维护局部最优解
for(int j = 0; j < n; j++) {
char digit = num[j];
// 注意:必须先判断 !st.empty(),防止栈空时调用 st.top() 导致段错误
while(!st.empty() && st.top() > digit && k > 0) {
st.pop();
k--;
}
st.push(digit);
}
// 2. 如果 k 还没用完,说明当前序列单调递增,从末尾继续删除
while(k > 0 && !st.empty()) {
st.pop();
k--;
}
// 3. 将栈中元素导出并反转
string res = "";
while(!st.empty()) {
res.push_back(st.top());
st.pop();
}
reverse(res.begin(), res.end());
// 4. 去除多余的前导零
int l = 0;
while(l < res.size() && res[l] == '0') {
l++;
}
res = res.substr(l);
// 5. 判空防错,若删光了则返回 "0"
return res.empty() ? "0" : res;
}
};
四、 复杂度分析
- 时间复杂度 :O(N)O(N)O(N),其中 NNN 是字符串
num的长度。每个字符最多被入栈和出栈一次。 - 空间复杂度 :O(N)O(N)O(N),栈和结果字符串在最坏情况下需要占用线性级别的存储空间。