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。
C++ 详细解析 一般方法&单调栈方法 移掉k位数字
解题思路
本题可以从三个方法去解题。
方法一:一般方法;
方法二:单调栈
方法三:(从string角度的)单调栈
代码
方法一:一般方法
这是我看到这道题的第一反应。
一开始,我以为只要将num
中k
个最大的数字逐一删去即可。
后来发现此思路行不通,因为这样无法保证剩余的数字按照原来的相对顺序生成最小的数字。例如: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:用于提速的几种方法:
- 可以先将num中,下标小于等于
k
的0
及前面的数字给移除。例如:num = 12010, k = 2的时候,可以将前面的12给剔除掉,那后面的0也会被消掉。这样必然比其他消除方案得到的数字要小。因为消除了3位数(附送了一个0)。
- 当返回的下标
min_index
及后面的字符串的长度 = 还需要保留的字符串的长度的时候,直接返回。
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:
- 当所有元素都进行过栈的处理之后,如果结果stack中的元素比要保留的长度要长的话,则把栈顶元素pop掉。
- 在入栈的时候,可忽略掉前置0.
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)
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!!