zoukankan      html  css  js  c++  java
  • 移掉k位数字

    402. 移掉K位数字

    难度中等

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

    注意:

    • 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。
    
    通过次数26,707
     
    提交次数89,032

    C++ 详细解析 一般方法&单调栈方法 移掉k位数字

    sleeping monster发布于 2020-04-132.8kC++贪心算法

    解题思路

    本题可以从三个方法去解题。
    方法一:一般方法;
    方法二:单调栈
    方法三:(从string角度的)单调栈

    代码

    方法一:一般方法

    这是我看到这道题的第一反应。
    一开始,我以为只要将numk个最大的数字逐一删去即可。
    后来发现此思路行不通,因为这样无法保证剩余的数字按照原来的相对顺序生成最小的数字。
    例如:num = 1432219, k=3时,得到的是1221 而不是最优的1219

    正确的思路应该是在num中逐步选取最小的数字,以组成最小的数字。
    而这里的重点,在于要在一定的范围内逐步选取最小的数字。(否则,上面的例子同样会得到错误的1221)

    所以,可以这样理解:
    在第一次循环中,num = 1432219, k=3,此时我们要删去k=3个数字,保留n-k=7-3=4个数字。
    那么,可以将num = 1432219分为 num = 1432 219两部分(k+1个数字+n-k-1个数字),因为在前面的4个数字里面,至少要选择1个最小的数字(在选取最后3个保留的前提下)进行保留,所以此时start = 0, end = k。也就是要在[start,end]中选取一个最小的进行保留,返回最小数字的下标min_index。
    而在下次循环中,start = min_index+1, end++ ,也就是在[start,end]中选择下一个最小的数字。以此类推,直至所有数字都选取完毕。

    TIPS:用于提速的几种方法:

    1. 可以先将num中,下标小于等于k0及前面的数字给移除。
      例如:num = 12010, k = 2的时候,可以将前面的12给剔除掉,那后面的0也会被消掉。这样必然比其他消除方案得到的数字要小。因为消除了3位数(附送了一个0)。
    2. 当返回的下标min_index及后面的字符串的长度 = 还需要保留的字符串的长度的时候,直接返回。

    微信图片_20200413215529.png

    int find_min(string num, int start, int end)//在[start,end]中找出最小的数字,返回下标
    {
    	int min = num[start];
    	int min_index = start;
    	for (int j = start; j <= end; j++)//找到第一个最小的字符
    	{
    		if (num[j] < min)
    		{
    			min = num[j];
    			min_index = j;
    		}
    	}
    	return min_index;
    }
    string removeKdigits(string num, int k) {
    	int pos = num.find_first_of('0');//返回的是size_t,若果没找到,返回npos
    	//可以先将num中,下标小于等于`k`的`0`及前面的数字给移除。
    	while (pos != num.npos && pos <= k && k)
    	{
    		num = num.substr(pos + 1);
    		k -= pos;
    		pos = num.find_first_of('0');
    	}
    	if (k == 0)
    		return num == "" ? "0" : num;
    	if (k >= num.size())
    		return "0";
    	string result;
    	int start = 0;
    	int end = k ;
    	while (result.size() < num.size() - k)
    	{
    		int min_index = find_min(num, start, end);
    		//当返回的下标`min_index`及后面的字符串的长度 = 还需要保留的字符串的长度的时候,直接返回:
    		if (num.size() - k == num.size() - min_index + result.size())
    			return result + num.substr(min_index);
    		result += num.substr(min_index, 1);
    		start = min_index + 1;
    		end++;
    	}
    	if (result == "")
    		result = "0";
    	return result;
    	
    }
    

    方法二:用stack实现的单调栈

    在看了其他大佬的题解之后,发现此题可以用单调栈来解决!!(就是想不到啊...)
    经过思考,我理解了可以使用单调栈的思路:
    比较a和b的大小,是从最高位开始进行比较的。
    那么,我们也应该是从最高位开始进行删数。所以,就是对num进行单调上升栈的维护。
    逐个数字入栈,当发现当前入栈元素<栈顶元素s.top()的时候,就s.pop(),维护栈的单调递增性。
    这样就可以保证,结果的最高位最小,并以此递增。
    TIPS:

    1. 当所有元素都进行过栈的处理之后,如果结果stack中的元素比要保留的长度要长的话,则把栈顶元素pop掉。
    2. 在入栈的时候,可忽略掉前置0.

    微信图片_20200413215529.png

    string removeKdigits(string num, int k) {
    	stack<char> s;
    	for (int i = 0; i < num.size(); i++)
    	{
    		while (!s.empty() && s.top() > num[i] && k)
    		{
    			s.pop();
    			k--;
    		}
    		if (s.empty() && num[i] == '0')
    			continue;//跳过前置0
    		s.push(num[i]);
    	}
    	string result;
    	while (!s.empty())
    	{
    		if (k > 0)//当还要再移除数字的时候:从此时单调递增栈的top部删去数字
    			k--;
    		else if (k == 0)//当不用再移除数字的时候:把字符串取出来到result
    			result += s.top();
    
    		s.pop();	
    	}
    	reverse(result.begin(), result.end());//stl中的reverse函数
    	return result == "" ? "0" : result;
    }
    

    方法三:用string实现的单调栈

    本方法是方法二的优化。即不用初始化一个栈,而是直接用string来实现栈的功能:维护单调上升的序列。
    这样可以降低时间复杂度!!(因为不需要把字符从栈里面取出来,并进行reverse)

    微信图片_20200413215529.png

    class Solution {
    public:
    string removeKdigits(string num, int k)
    {
    	string result;
    	for (int i = 0; i < num.size(); i++)
    	{
    		while (result.size() && k&&result.back() > num[i])
    		{
    			result.pop_back();
    			k--;
    		}
    		if (result.size() == 0 && num[i] == '0')
    			continue;
    		result+=num[i];
    	}
        while (k > 0 && !result.empty())
    	{
    		result.pop_back();
    		k--;
    	}
    	return result == "" ? "0" : result;
    }
    };
    

    如果对你有帮助或启发的话,不妨点个赞~
    Thanks for your reading!!

    下一篇:c++ 贪心+用vecto
    主动一点,世界会更大!
  • 相关阅读:
    关于集合中的实现细节
    数组与内存控制笔记
    python进阶------进程线程(五)
    python进阶------进程线程(四)
    python进阶------进程线程(三)
    python进阶-------进程线程(二)
    python进阶------进程线程(一)
    python进阶---Python中的socket编程
    Python基础---python中的异常处理
    Python进阶---面向对象第三弹(进阶篇)
  • 原文地址:https://www.cnblogs.com/sweet-li/p/13690688.html
Copyright © 2011-2022 走看看