移掉 K 位數字


給定一個以字符串表示的非負整數 num,移除這個數中的 k 位數字,使得剩下的數字最小,其中java


解題思路

首先咱們要了解一個關於數學的前置知識,對於兩個相同長度的數字序列,最左邊不一樣的數字決定了這兩個數字的大小,例如,對於 A = 1axxxA = 1axxx,B = 1bxxxB = 1bxxx,若是 a > b,則 A > Bgit

基於此,咱們能夠知道,若要使得剩下的數字最小,須要保證靠前的數字儘量小算法

若是使用暴力法,那思路就是:app

  • 從左到右遍歷
  • 對於每個遍歷到的元素,前一個元素比當前元素大,則丟棄前一個元素,不然保留前一個元素

須要注意的是,若是給定的數字是一個單調遞增的數字,那麼咱們的算法會永遠選擇不丟棄。這個題目中要求的,咱們要永遠確保丟棄 k 個數字,所以思路還應該稍加修改:ui

  • 每次丟棄一次,k 減去 1。當 k 減到 0 ,咱們能夠提早終止遍歷
  • 而當遍歷完成,若是 k 仍然大於 0。不妨假設最終還剩下 x 個須要丟棄,那麼咱們須要選擇刪除末尾 x 個元素

然而暴力的實現複雜度最差會達到 O(nk)(考慮整個數字序列是單調不降的),所以咱們須要加速這個過程code

能夠用一個棧維護當前的答案序列,棧中的元素表明截止到當前位置,刪除不超過 k 次個數字時,所能獲得的最小整數。根據以前的討論:在使用 k 個刪除次數以前,棧中的序列從棧底到棧頂單調不降。所以,對於每一個數字,若是該數字小於棧頂元素,咱們就不斷地彈出棧頂元素,直到rem

  • 棧爲空
  • 新的棧頂元素不大於當前數字
  • 已經刪除了 k 位數字

上述步驟結束後咱們還須要針對一些狀況作額外的處理:字符串

  • 若是咱們刪除了 m 個數字且 m<k,咱們須要從序列尾部刪除額外的 k-m 個數字
  • 若是最終的數字序列存在前導零,咱們要刪去前導零
  • 若是最終數字序列爲空,咱們應該返回 0
class Solution {

    public String removeKdigits(String num, int k) {
        Deque<Character> deque = new LinkedList<>();
        for(int i = 0; i < num.length(); i++) {
            while(!deque.isEmpty() && k > 0 && deque.peekLast() > num.charAt(i)) {
                deque.pollLast();
                k--;
            }
            deque.offerLast(num.charAt(i));
        }
        for(int i = 0; i < k; i++) {
            deque.pollLast();
        }
        StringBuilder str = new StringBuilder();
        boolean leadingZero = true;
        while(!deque.isEmpty()) {
            char digist = deque.pollFirst();
            if(leadingZero && digist == '0') {
                continue;
            }
            leadingZero = false;
            str.append(digist);
        }
        return str.length() == 0 ? "0" : str.toString();
    }
}