402. 移掉 K 位数字(中等)

Posted 炫云云

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了402. 移掉 K 位数字(中等)相关的知识,希望对你有一定的参考价值。

402. 移掉 K 位数字(中等)

316. 去除重复字母

321. 拼接最大数

402. 移掉 K 位数字(中等)

我们从一个简单的问题入手,识别一下这种题的基本形式和套路,为之后的三道题打基础。

题目描述

给定一个以字符串表示的非负整数  num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:

num 的长度小于 10002 且  ≥ 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。

思路

这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。

以题目中的 num = 1432219, k = 3 为例,我们需要返回一个长度为 4 的字符串,问题在于: 我们怎么才能求出这四个位置依次是什么呢?

图 1 图 1 1

暴力法的话,我们需要枚举C_n^(n - k) 种序列(其中 n 为数字长度),并逐个比较最大。这个时间复杂度是指数级别的,必须进行优化。

一个思路是:

  • 从左到右遍历
  • 对于每一个遍历到的元素,我们决定是丢弃还是保留

问题的关键是:我们怎么知道,一个元素是应该保留还是丢弃呢?

这里有一个前置知识:对于两个数 123a456 和 123b456,如果 a > b, 那么数字 123a456 大于 数字 123b456,否则数字 123a456 小于等于数字 123b456。也就说,两个相同位数的数字大小关系取决于第一个不同的数的大小。

因此我们的思路就是:

  • 从左到右遍历
  • 对于遍历到的元素,我们选择保留。
  • 但是我们可以选择性丢弃前面相邻的元素。
  • 丢弃与否的依据如上面的前置知识中阐述中的方法。

以题目中的 num = 1432219, k = 3 为例的图解过程如下:

图 2 图 2 2

图 2:由于没有左侧相邻元素,因此没办法丢弃

图 3 图 3 3

图 3:由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃

图 4 图 4 4

图 4:由于 3 比左侧相邻的 4 小。 如果选择丢弃左侧的 4,那么会使得剩下的数字更小(开头的数从 4 变成了 3)。因此我们选择丢弃

。。。

后面的思路类似,我就不继续分析啦。

然而需要注意的是,如果给定的数字是一个单调递增的数字,那么我们的算法会永远选择不丢弃。这个题目中要求的,我们要永远确保丢弃 k 个矛盾。

一个简单的思路就是:

  • 每次丢弃一次栈顶,k 减去 1。当 k 减到 0 ,我们可以提前终止遍历。
  • 而当遍历完成,如果 k 仍然大于 0。不妨假设最终还剩下 x 个需要丢弃,那么我们需要选择删除末尾 x 个元素。

我们需要把思路逆转过来。刚才我的关注点一直是丢弃,题目要求我们丢弃 k 个。反过来说,不就是让我们保留 n − k n - k nk 个元素么?其中 n 为数字长度。 那么我们只需要按照上面的方法遍历完成之后,再截取前n - k个元素即可。

按照上面的思路,我们来选择数据结构。由于我们需要保留丢弃栈顶的元素,因此使用栈这种在一端进行添加和删除的数据结构是再合适不过了,我们来看下代码实现。

class Solution(object):
    def removeKdigits(self, num, k): #删除k个数
        stack = []
        remain = len(num) - k
        for digit in num:
            while k and stack and stack[-1] > digit: # 当前数据左边比右边大,丢弃左边,直到k=0
                stack.pop()
                k -=1
            stack.append(digit)
        
        return ''.join(stack[:remain]).lstrip('0') or '0'
                

复杂度分析

  • 时间复杂度:虽然内层还有一个 while 循环,但是由于每个数字最多仅会入栈出栈一次,因此时间复杂度仍然为 O ( N ) O(N) O(N),其中 N N N 为数字长度。
  • 空间复杂度:我们使用了额外的栈来存储数字,因此空间复杂度为 O ( N ) O(N) O(N),其中 N N N 为数字长度。

提示: 如果题目改成求删除 k 个字符之后的最大数,我们只需要将 stack[-1] > digit 中的大于号改成小于号即可。

参考

Krahets - 力扣(LeetCode) (leetcode-cn.com)

以上是关于402. 移掉 K 位数字(中等)的主要内容,如果未能解决你的问题,请参考以下文章

402. 移掉K位数字

402. 移掉 K 位数字-贪心

402. 移掉 K 位数字

Leetcode 402. 移掉K位数字

LeetCode 0402. 移掉 K 位数字

LeetCode402移除K位数字