leetcode:402. 移掉 K 位数字

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    string removeKdigits(string num, int k){

    }
};

题目解析

思路

如果n是 num 的长度,我们要去除k个,那么需要剩下 n-k 个,怎么判断哪些数字应该去掉呢?

  • 首先来考虑,若数字是递增的话,比如 1234,那么肯定是要从最后面移除最大的数字。
  • 若是乱序的时候,比如 1324,若只移除一个数字,移除谁呢?这个例子比较简单,我们一眼可以看出是移除3,变成 124 是最小。

所以:

  • 维护一个递增栈,只要发现当前数字 < 栈顶元素,那么就移除栈顶元素。为什么呢?
    • 因为要数字最小,所以我们要让高位尽可能变小
    • 所以如果这个字符串是递增的,没什么好说的,擦除当前大的这个数,让小的到高位去,比如[9],1,2,3,一定移除9,让栈顶边1
    • 如果后面可能出现比栈顶元素高的元素,因为此时栈顶元素在高位上,就算后面的数字再大,也是在低位上,我们只有将高位上的数字尽可能的变小,才能使整个剩下的数字尽可能的小。比如 [3], 1 9 7 ,一定移除3,让栈顶变1

思路:

  • 从左至右扫描,当前扫描的数还不确定要不要删,入栈暂存。
  • 123531这样「高位递增」的数,肯定不会想删高位,会尽量删低位。
  • 432135这样「高位递减」的数,会想干掉高位,直接让高位变小,效果好。
  • 所以,如果当前遍历的数比栈顶大,符合递增,是满意的,让它入栈。
  • 如果当前遍历的数比栈顶小,栈顶立刻出栈,不管后面有没有更大的,为什么?

因为栈顶的数属于高位,删掉它,小的顶上,高位变小,效果好于低位变小。

"1432219"  k = 3
bottom[1       ]top		1入
bottom[1 4     ]top		4入
bottom[1 3     ]top	43入
bottom[1 2     ]top	32入
bottom[1 2 2   ]top		2入  
bottom[1 2 1   ]top	21入	出栈满3个,停止出栈
bottom[1 2 1 9 ]top		9

照这么设计,如果是"0432219",0 遇不到比它更小的,最后肯定被留在栈中,变成 0219,还得再去掉前导0。

"0432219"  k = 3
bottom[0       ]top		0入
bottom[0 4     ]top		4入
bottom[0 3     ]top	43入
bottom[0 2     ]top	32入
bottom[0 2 2   ]top		2入  
bottom[0 2 1   ]top	21入	出栈满3个,停止出栈
bottom[0 2 1 9 ]top		9

能不能直接不让前导 0 入栈?——可以。

加一个判断:栈为空且当前字符为 “0” 时,不入栈。取反,就是入栈的条件:

if c != '0' || len(stack) != 0 {
	stack = append(stack, c) // 入栈
}

这避免了 0 在栈底垫底。
注意到,遍历结束时,有可能还没删够 k 个字符,继续循环出栈,删低位。
删够了,但如果栈变空了,什么也不剩,则返回 “0”。
否则,将栈中剩下的字符,转成字符串返回。

class Solution {
public:
    string removeKdigits(string num, int k){
       std::string ans;
       int n = num.size(), keep = n - k;
       for(char c : num){
           while (k && !ans.empty() && ans.back() > c){
               ans.pop_back();
               k--;
           }
           if(!ans.empty() || c != '0'){
               ans.push_back(c);
           }
       }
       while (!ans.empty() && k-- ){
           ans.pop_back();
       }
       return ans.empty() ? "0" : ans;
    }

};

思路

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

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

在这里插入图片描述

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

一个思路是:

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

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

前置知识:

  • 对于两个数 123a456 和 123b456
    • 如果a > b,那么数字 123a456 > 123b456;
    • 如果a < b,那么数字 123a456 < 123b456;
  • 也就是说,两个相同位数的数字大小关系取决于第一个不同的数的大小

因此我们的思路就是:

  • 从左到右遍历
  • 对于遍历到的元素,我们选择保留。 但是我们可以选择性丢弃前面相邻的元素。丢弃与否的依据如上面的前置知识中阐述中的方法。
  • 也就是说,对于遍历到的元素(从第二个元素开始),与前面的元素进行比较
    • 如果比前一个元素小,就把前一个元素[替换成当前小的元素]
    • 如果大于等于前一个元素,则该元素直接拼接到最后面

以题目中的 num = 1432219, k = 3 为例的图解过程如下:
在这里插入图片描述

  • 由于没有左侧相邻元素,因此没办法丢弃。
    在这里插入图片描述

  • 由于 4 比左侧相邻的 1 大。如果选择丢弃左侧的 1,那么会使得剩下的数字更大(开头的数从 1 变成了 4)。因此我们仍然选择不丢弃(因为字典序要求最小)
    在这里插入图片描述

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

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

一个简单的思路是:

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

上面的思路可行,但是稍显复杂。

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

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

在这里插入图片描述

什么时候用单调栈?

  • 需要给当前的元素,找右边/左边第一个比它大/小的位置。
    • 单调递增栈,利用波谷剔除栈中的波峰,留下波谷;
    • 单调递减栈,利用波峰剔除栈中的波谷,留下波峰。
  • 本题想维护高位递增,即,元素想找右边第一个比它小的数,即右侧第一个波谷。
    • 单调递增栈遇到波谷,用它来剔除栈中的波峰,维持单增。
    • 留下波谷保持了单增,而剔除掉的栈中字符,就是删掉的字符。

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值