Leetcode 402-移掉 K 位数字
给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
题解(贪心+单调栈)
前后数字逆序,并且要删除若干个数字可以考虑单调栈
对于两个相同长度的数字序列,最左边不同的数字决定了这两个数字的大小, 例如,对于 A=1axxx,B=1bxxx,如果 a>b 则 A>B。
基于此,我们可以知道,若要使得剩下的数字最小,需要保证靠前的数字尽可能小。 考虑从左往右增量的构造最后的答案。我们可以用一个栈维护当前的答案序列,栈中的元素代表截止到当前位置,删除不超过 k 次个数字后,所能得到的最小整数。根据之前的讨论:在使用 k 个删除次数之前,栈中的序列从栈底到栈顶单调不降。
因此,对于每个数字,如果该数字小于栈顶元素,我们就不断地弹出栈顶元素,直到
-
栈为空 或者新的栈顶元素不大于当前数字 或者我们已经删除了 k 位数字
上述步骤结束后我们还需要针对一些情况做额外的处理:
-
如果我们删除了 m 个数字且 m<k,这种情况下我们需要从序列尾部删除额外的k−m 个数字。 如果最终的数字序列存在前导零,我们要删去前导零。 如果最终数字序列为空,我们应该返回 0。 最终,从栈底到栈顶的答案序列即为最小数。
考虑到栈的特点是后进先出,如果通过栈实现,则需要将栈内元素依次弹出然后进行翻转才能得到最小数。为了避免翻转操作,可以使用双端队列代替栈的实现。(这样做也方便进行前导零的处理)
class Solution { public String removeKdigits(String num, int k) { int len = num.length(); Deque<Character> deque = new LinkedList<>(); //用StringBuilder而不是String StringBuilder res = new StringBuilder(); int temp=k; //用标志位判断数字开头是否为零,初始值应该设置为true boolean beginZero = true; for(int i=0;i<len;i++){ //1.stack不为空且2.stack小于栈顶元素且3.k>0 while(!deque.isEmpty()&&num.charAt(i)<deque.peekLast()&&k>0){ deque.pollLast(); k--; } deque.offerLast(num.charAt(i)); } //1.还要考虑数组一直递增的特殊情况,所以只取栈前len-temp位 //2.用标志位判断数字开头是否为零 for(int i=0;i<len-temp;i++){ //此处要存一下deque.pollFirst(),否则下文if判断和append操作可能会使得deque弹出两次 Character digit = deque.pollFirst(); if(beginZero&&digit==0) continue; //说明数字开头前几个为零的数字已经被跳过 beginZero=false; res.append(digit); } return res.isEmpty() ? "0" : res.toString(); } }