leetcode-移掉 K 位数字

294 阅读3分钟

题目描述

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

  示例 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 。

提示:

  • 1 <= k <= num.length <= 105
  • num 仅由若干位数字(0 - 9)组成
  • 除了 0 本身之外,num 不含任何前导零

思路

首先,无论移除哪k位,移除后数字的位数是一样的,依然是remain位(假设先允许前导0存在)。
那么,最简单粗暴的方法是,枚举移除k位数字的后的所有可能,然后遍历一遍选择出最小的。但是,这样的方法在num位数较多的时候,移除k位后的可能呈指数增长,时间复杂度和空间复杂度都接受不了。所以,本题的难点在于如何快速判断应该移除哪k位。
对于2个数位相同的数组,大小只取决于从高到低第1个对应位置上不同数字。例如整数157a690 和 157b690,大小只取决于第1个对应位置上不同数字,这里就是 a 和 b。
所以本题可以使用单调栈的思想,如果第m位数字 > 第m+1位数字,那么移除第m位数字是划算的,因为可以让这一位的数字变小,因为位数从高到低刚好是从左往右,所以按照顺序遍历时,刚好是先处理高位,即影响更大的位置。
我们的策略是,定义1个栈stack,从高到低遍历原始数字,如果这1位数字 < 当前栈顶数字,我们就移除栈顶元素,直到栈为空或者 当前值 >= 栈顶元素。注意,移除最多只能k位,如果已经移除k位了,就不再执行移除逻辑。 当然,另一种情况下,维护完整个单调栈,移除的数量还不到k位,那么我们取出单调栈的数值并取前remain = len - k位即可。因为这里要按照放入的顺序取出栈中的元素,我们可以按照普通栈取出后在reverse,或者使用双端队列来替代栈,直接按照顺序来取出。
最后,还要注意结果存在前导0的情况,要去除前导0。
我们用图来展示一下上面的方法,更加直观:

步骤1.png

步骤2.png

步骤3.png

步骤4.png

步骤5.png

步骤6.png

Java版本代码

class Solution {
    public String removeKdigits(String num, int k) {
        int remain = num.length() - k;
        if (remain == 0) {
            return "0";
        }
        // 无论移除哪k位,移除后数字的位数是一样的,依然是remain位(假设先允许前导0存在)
        // 2个位数一样的数字,大小只取决于从高到低第1个对应位置上不同数字
        // 如果 第m位数字 > 第m+1位数字,那么移除第m位数字是划算的
        Deque<Integer> deque = new LinkedList<>();
        for (char c : num.toCharArray()) {
            int val = Integer.parseInt(String.valueOf(c));
            while (k > 0 && !deque.isEmpty() && deque.peekLast() > val) {
                deque.pollLast();
                k--;
            }
            deque.offerLast(val);
        }
        // 从双端列队中获取前remain位数字,同时去除前导0
        StringBuilder ans = new StringBuilder();
        // 前导0标识,如果是false,证明前面已经有不是0的数字了
        boolean startZero = true;
        for (int i = 0; i < remain; i++) {
            int val = deque.pollFirst();
            if (val == 0) {
                // 当前数字是0,要判断前导0标识,如果是true,证明是前导0,不能添加到ans中;否则,前面已经存在不是0的数字了,可以添加
                if (!startZero) {
                    ans.append(val);
                }
            } else {
                // 当前数字不是0,如果前导0标识是true,要修改为false
                if (startZero) {
                    startZero = false;
                }
                ans.append(val);
            }
        }
        // 去除前导0后,可能全部被去除
        if (ans.length() == 0) {
            return "0";
        }
        return ans.toString();
    }
}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 4 天,点击查看活动详情