刪除k個數字后的最小值
摘自漫畫算法:
題目:給出一個整數,從該整數中去掉k個數字,要求剩下的數字形成的新整數盡可能小,應該如何選取被去掉的數字?
其中整數的長度大于或等于k,給出的整數大小可以超過long類型的數字范圍。什么意思?
例子:
假設給出一個整數1593212,刪去3個數字,新整數最小的情況是1212。
假設給出一個整數30200,刪去1個數字,新整數最小的情況是200。
假設給出一個整數10,刪去2個數字(注意,這里要求刪去的不是1個數字,而是2個),新整數的最小情況是0。
解題思路
這個題目要求我們刪去k個數字,但我們不妨把問題簡化一下:如果只刪除1個數字,如何讓新整數的值最小?
我的第一感覺是優先刪除最大的數字,但是不對。
注意:數字的大小固然重要,數字的位置則更加重要。大家可以想一想,一個整數的最高位哪怕只減少1位,對數值的影響也是非常大的。
例子:
給出一個整數541270936,要求刪去1個數字,讓剩下的整數盡可能小。
此時,無論刪除哪一個數字,最后的結果都是從9位整數變成8位整數。既然同樣是8位整數,顯然應該優先把高位的數字降低,這樣對新整數的值影響最大。
如何把高位的數字降低呢?很簡單,把原整數的所有數字從左到右進行比較,如果發現某一位數字大于它右面的數字,那么在刪除該數字后,必然會使該數位的值降低,因為右面比它小的數字頂替了它的位置。
在上面這個例子中,數字5右側的數字小于5,所以刪除數字5,最高位數字降低成了4。
對于整數541270936,刪除一個數字所能得到的最小值是41270936。那么對于41270936,刪除一個數字的最小值是多少呢?
接下來,從剛才的結果1270936中再刪除一個數字,能得到的最小值又是多少呢?
由于1<2,2<7,7>0,所以被刪除的數字為7。
這里的每一步都要求得到刪除一個數字后的最小值,經歷3次,相當于求出了刪除k(k=3)個數字后的最小值。
像這樣依次求得局部最優解,最終得到全局最優解的思想,叫作貪心算法。
代碼實現
/**
* 描述:刪除k個數字后的最小值問題。
* <p>
* Create By ZhangBiao
* 2020/6/8
*/
public class RemoveKDigits {
/**
* 刪除整數的k個數字,獲取刪除后的最小值
*
* @param num 原整數
* @param k 刪除數量
* @return
*/
public static String removeKDigits(String num, int k) {
// 新整數的最終長度 = 原整數長度 - k
int newLength = num.length() - k;
// 創建一個棧,用于接收所有的數字
char[] stack = new char[num.length()];
int top = 0;
for (int i = 0; i < num.length(); i++) {
// 遍歷當前數字
char c = num.charAt(i);
// 當棧頂數字大于遍歷到的當前數字時,棧頂數字出棧(相當于刪除數字)
while (top > 0 && stack[top - 1] > c && k > 0) {
top -= 1;
k -= 1;
}
// 遍歷到的當前數字入棧
stack[top++] = c;
}
// 找到棧中第1個非零數字的位置,依次構建新的字符串
int offset = 0;
while (offset < newLength && stack[offset] == '0') {
offset++;
}
return offset == newLength ? "0" : new String(stack, offset, newLength - offset);
}
public static void main(String[] args) {
System.out.println(removeKDigits("1593212", 3));
System.out.println(removeKDigits("30200", 1));
System.out.println(removeKDigits("10", 2));
System.out.println(removeKDigits("541270936", 3));
}
}
上述代碼非常巧妙地運用了棧的特性,在遍歷原整數的數字時,讓所有數字一個一個入棧,當某個數字需要刪除時,讓該數字出棧。最后,程序把棧中的元素轉化為字符串類型的結果。
下面仍然以整數541270936,k=3為例:
- 當遍歷到數字5時,數字5入棧。
- 當遍歷到數字4時,發現棧頂5>4,棧頂5出棧,數字4入棧。
- 當遍歷到數字1時,發現棧頂4>1,棧頂4出棧,數字1入棧。
- 然后繼續遍歷數字2、數字7,并依次入棧。
- 最后,遍歷數字0,發現棧頂7>0,棧頂7出棧,數字0入棧。
- 此時k的次數已經用完,無序再次比較,讓剩下的數字一起入棧即可。
此時棧中的元素就是最終的結果。
上面的方法只對所有數字遍歷了一次,遍歷的時間復雜度是O(n),把棧轉化為字符串的時間復雜度也是O(n),所以最終的時間復雜度是O(n)。
同時,程序中利用棧來回溯遍歷過的數字及刪除數字,所以程序的空間復雜度是O(n)。