leetcode402. Remove K Digits
题目描述如下:
Given a non-negative integer num represented as a string, remove k digits from the number so that the new number is the smallest possible.
Note:
The length of num is less than 10002 and will be ≥ k.
The given num does not contain any leading zero.
Example 1:
Input: num = “1432219”, k = 3
Output: “1219”
Explanation: Remove the three digits 4, 3, and 2 to form the new number 1219 which is the smallest.
Example 2:
Input: num = “10200”, k = 1
Output: “200”
Explanation: Remove the leading 1 and the number is 200. Note that the output must not contain leading zeroes.
Example 3:
Input: num = “10”, k = 2
Output: “0”
Explanation: Remove all the digits from the number and it is left with nothing which is 0.
题目的描述很简单,从一个数字中去掉k个数,然后让剩下来的数最小,这道题用穷举的方法肯定是行不通的,因为k个数有很多种可能,复杂度为阶乘级别,所以比较直接的想法是采用动态规划或者是贪心算法。
首先我们考虑只去掉一个数的情况,要让剩下来的数最小,我们肯定是想让先让前面的数尽可能的小,所以如果从第1位到第k位是呈递增状态的,我们肯定不会先考虑去掉1k位之间的数字,因为如果这样去掉的话就相当于让大的数“挪”到前面来了,显然比保留1k位,然后去掉其他的随便一个数要大,而具体要去掉哪个,我们先假设现在1k位已经是递增的了,然后k+1位小于第k位,如果不去掉第k位,那么前1k位这部分数就永远都不可能小于去掉k位,然后k位被k+1位补上所构成的数大,所以我们可以看出我们要去掉的数正是从前往后遍历所发现的第一个“峰”,也就是数字比左右两边都大的数。
然后我们再考虑多去几位的情况,如果我们不按上面对于一步最优的策略来去除数字,也就是上面的第k位还是被保留了下来,那么现在就可能出现两种情况,第一种情况是第k位依然是峰,那么在最后一步的时候也是迟早要被去掉的,所以还不如早点去掉,第二种情况是因为去掉其他的数字让第k位的邻居变成了比它还大的数字,那么这种情况肯定有第k位和这个更大的数字之间的数被去掉了,并且这个被去掉的数(或者说是之一)肯定是比k位小的,否则第k位一开始就不可能是峰,那么既然如此把第k位换成这个更小的数本身就是一个方案,并且得到的数比保留第k位要小,所以可以得出结论,只要不断利用上面对于单步的方法处理多步的字符串,那么就可以得到最终的最小的数字。
而每进行一步就进行从头开始遍历显然是不明智的,我们可以采用维护一个栈的方法,栈里的数字始终是递增的,如果新入栈的数小于栈顶,就去掉栈顶的数,然后再进一步判断,直到去掉指定数量的数字,那么剩下的数字就构成了最小的数字字符串。
代码实现如下;
class Solution
{
public:
string removeKdigits(string num, int k)
{
string ans;
int index = 1;
ans.push_back(num[0]);
while(k)
{
//如果入栈的数始终递增,则不断入栈
while(index < num.size() && ans.back() <= num[index])
{
ans.push_back(num[index]);
index++;
}
if(index == num.size())
break;
//去掉栈顶
k--;
ans.pop_back();
//如果去掉栈顶后新的栈顶还是比新入栈的数要大,继续抛弃直到达到指定数量的数字
while(k && !ans.empty() && ans.back() > num[index]){
ans.pop_back();
k--;
}
//新元素入栈,形成新的递增序列
ans.push_back(num[index]);
index++;
}
//存在因为k过小导致没有遍历整个字符串就已经确定去掉的数的情况,把后面没有遍历过的数拼接上
ans += num.substr(index);
//去掉前导0
index = 0;
while(index < ans.size() && ans[index] == '0'){
index++;
}
ans = ans.substr(index);
//如果k过大导致遍历完整个字符串后都没找全要去掉的数,这样就说明剩下的所有数都是递增的了,那么就直接去掉后面k个即可
if(k)
ans = ans.substr(0, ans.size() - k);
if(ans.empty())
return "0";
return ans;
}
};
因为只遍历了一遍字符串,所以时间复杂度为O(n),空间复杂度也为O(n)