1、移除K位數字
題目:402. 移掉K位數字
題目描述:
給定一個以字符串表示的非負整數 num,移除這個數中的 k 位數字,使得剩下的數字最小。(num 的長度小於 10002 且 ≥ k。
num 不會包含任何前導零。)
示例 :
輸入: num = "1432219", k = 3
輸出: "1219"
解釋: 移除掉三個數字 4, 3, 和 2 形成一個新的最小的數字 1219。
輸入: num = "10200", k = 1
輸出: "200"
解釋: 移掉首位的 1 剩下的數字為 200. 注意輸出不能有任何前導零。
輸入: num = "10", k = 2
輸出: "0"
解釋: 從原數字移除所有的數字,剩余為空就是0。
解題思路:基於單調棧的貪心算法
對於兩個長度相同的數字,其最左邊的數字決定了這兩個數字的大小,左邊的數字越小,數字也越小,比如如果由相同的元素組成的數字,升序序列是最小的。所以我們的想法是盡可能地使左邊的數字小。
所以,給定一個數字序列[a,b,c,d,e,f],如果數字b小於其左鄰居 a,則我們應該刪除左鄰居a,以獲得最小結果。

對於每個數字,如果該數字小於棧頂部,即該數字的左鄰居,則彈出堆棧,即刪除左鄰居。否則,我們把數字推到棧上。
重復上述步驟,直到任何條件不再適用,例如堆棧為空(不再保留數字)。或者已經刪除了 k 位數字。
同時,這里需要注意一些特殊情況:
1、如果整個序列是單增的,我們會發現左鄰居永遠小,所以應該刪除掉末尾的k個數字即可。
2、在完成整個循環后,棧中彈出的數字是逆序的,需要反轉,需要注意的是結果可能出現前導零,需要考慮到這種情況,將前導零刪除。

代碼實現:
class Solution {
public String removeKdigits(String num, int k) {
//基於單調棧的貪心思想
if(k==0)
return num;
if(num.length()==0 || k>=num.length())
return "0";
Stack<Character> stack=new Stack<>(); //維護一個單調棧
for(int i=0;i<num.length();i++){
char c=num.charAt(i);
while(!stack.isEmpty() && stack.peek()>c && k>0 ){
stack.pop(); //左邊的大則刪除,本質是貪心
k--;
}
stack.push(c);
}
//特別注意:如果沒有刪夠k次,說明剩下的是單調不減的,則從末尾刪除即可
for(int i=0;i<k;i++) //剩余多少次由k決定
stack.pop();
//最后將棧中的元素彈出並反轉即可
StringBuffer str=new StringBuffer();
while(!stack.isEmpty()){
str.append(stack.pop());
}
str=str.reverse(); //反轉
//去除前導0
int index=0;
while(index<str.length() && str.charAt(index)=='0')
index++;
String res=str.toString().substring(index);
return res.length()==0?"0":res;
}
}
2、字典序最大的子序列
題目描述:
給定⼀個字符串,得到它字典序最⼤的⼦序列。(換言之,也就是刪除⼀些字符,使得剩下的字符構成的字符串字典序是最⼤的)
解題思路:
兩個字符串相比,字典序的大小首先是對排在前列的字符進行比較,越靠前的字符越大,整體的字典序就越大,因此,要想字典序最大,應該將其變為一個單調遞減的子序列。
因此,這和上一題實際上類似的,只是沒有了刪除k次的限制,同樣應該利用一個單調棧,當棧頂數字小於當前考察的元素時,即將其棧頂數字刪除,最后在棧中形成一個單調遞減(嚴格來說是單調不增)的子序列。
代碼實現
與上題代碼基本類似,核心在於基於單調棧的貪心思想,越靠前的越大越好。