移掉 K 位数字
1、参考资料
https://leetcode-cn.com/problems/remove-k-digits/
2、题目要求
题目描述
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
- num 的长度小于 10000位 且 ≥ k。
- num 不会包含任何前导零。
示例 1
输入: num = "1432219", k = 3
输出: "1219"
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2
输入: num = "10200", k = 1
输出: "200"
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3
输入: num = "10", k = 2
输出: "0"
解释: 从原数字移除所有的数字,剩余为空就是0。
3、代码思路
LeetCode 上的题解写的不错:利用栈的贪心算法
对于两个相同长度的数字序列,最左边不同的数字决定了这两个数字的大小,例如,对于 A = 1axxx
,B = 1bxxx
,如果 a > b
则 A > B
。所以说我们尽量将最小的数字放在左边,这样才能保证删除 K 个数字后,使得剩余的数字值达到最小。知道了这个以后,我们可以想到,在删除数字时应该从左向右迭代。
比如数字序列 13254
,我们现在要求删除 2
个数字,即 K=2
,那么我们从左至右进行扫描,在整个过程中,我们维护一个栈 stack
用于存储筛选后的数字序列,维护一个变量 countLeft
用于记录当前还可以移除多少个数字
根据以上的描述,我们大致说一下程序的执行流程:
- 初始化一个
stack
用于存储数字序列,countLeft = K = 2
表示当前还剩两个数字可以移除,然后我们从左往右开始扫描数字序列 - 刚开始,
stack
为空,直接将数字1
压入栈中,我们此时并没有移除数字,所以无需修改countLeft
的值 - 扫描到第二个数字
3
,发现3
大于此时栈顶的元素1
,我们将3
压入栈中,此步也没有移除任何数字,所以无需修改countLeft
的值 - 扫描到第三个数字
2
,发现2
小于此时栈顶的元素3
,将2
放在1
后面组成的数字序列,肯定要比将3
放在1
后面组成的数字序列小,所以,我们将3
从栈顶移除,此步移除了数字3
,所以countLeft--
,然后我们继续将2
与栈顶元素相比较,发现2
大于此时的栈顶元素1
, 我们将2
压入栈中 - 扫描到第四个数字
5
,发现5
大于此时栈顶的元素2
,我们将5
压入栈中,此步也没有移除任何数字,所以无需修改countLeft
的值 - 扫描到第三个数字
4
,发现4
小于此时栈顶的元素5
,将4
放在2
后面组成的数字序列,肯定要比将5
放在2
后面组成的数字序列小,所以,我们将5
从栈顶移除此步移除了数字3
,所以countLeft--
,此时countLeft == 0
,我们已经移除了K
个数字,退出主循环
以下是来自 LeetCode 的图解
给定输入序列 [1,2,3,4,5,2,6,4]
和 k=4
,规则在 5
触发。删除数字 5
后,规则将在数字 4
处再次触发,直到数字 3
。然后,在数字 6
处,规则也被触发。
在上述主循环之外,我们需要处理一些情况,以使解决方案更加完整:
- 当我们离开主循环时,我们删除了
m
个数字,这比要求的要少,即(m<k
)。在极端情况下,我们不会删除循环中单调递增序列的任何数字,即m==0
。在这种情况下,我们只需要从序列尾部删除额外的k-m
个数字。 - 一旦我们从序列中删除
k
位数字,可能还有一些前导零。要格式化最后的数字,我们需要去掉前导零。 - 我们最终可能会从序列中删除所有的数字。在这种情况下,我们应该返回零,而不是空字符串。
4、代码实现
-
代码
/** * @ClassName RemoveKdigitsDemo * @Description TODO * @Author Heygo * @Date 2020/8/17 11:14 * @Version 1.0 */ public class RemoveKdigitsDemo { public static void main(String[] args) { String minDigits = removeKdigits("13254", 2); System.out.println(minDigits); } public static String removeKdigits(String num, int k) { LinkedList<Character> stack = new LinkedList<Character>(); int countLeft = k; // 还剩多少个数字可以移除 // 从左往右扫描数字序列 for (char digit : num.toCharArray()) { /* 看看当前字符(digit)是否小于栈顶元素 如果 stack.peekLast() > digit,那么肯定要删除 digit 左边的数字,尽量将 digit 往左放,才能使得数字序列最小 如果 stack.peekLast() <= digit,先将 digit 压入栈中,让它和其右边的数字进行比较 */ while (stack.size() > 0 && countLeft > 0 && stack.peekLast() > digit) { stack.removeLast(); // 移除栈顶元素 countLeft -= 1; // 可移除的数字个数减 1 } stack.addLast(digit); // 将当前数字压入栈中 } // 极端情况:如果原数字序列依次增,我们不会删除任何数字,所以这是需移除尾部元素 for (int i = 0; i < countLeft; ++i) { stack.removeLast(); } // 移除前导 0 StringBuilder ret = new StringBuilder(); boolean leadingZero = true; for (char digit : stack) { if (leadingZero && digit == '0') { continue; } leadingZero = false; ret.append(digit); } // 如果 k 和原数字序列长度相等,则我们只能得到一个空串 if (ret.length() == 0) { return "0"; } // 返回最小的数字序列 return ret.toString(); } }
-
程序运行结果
124