移掉 K 位數字

低吟不作語發表於2020-11-15

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


解題思路

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

基於此,我們可以知道,若要使得剩下的數字最小,需要保證靠前的數字儘可能小

如果使用暴力法,那思路就是:

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

需要注意的是,如果給定的數字是一個單調遞增的數字,那麼我們的演算法會永遠選擇不丟棄。這個題目中要求的,我們要永遠確保丟棄 k 個數字,因此思路還應該稍加修改:

  • 每次丟棄一次,k 減去 1。當 k 減到 0 ,我們可以提前終止遍歷
  • 而當遍歷完成,如果 k 仍然大於 0。不妨假設最終還剩下 x 個需要丟棄,那麼我們需要選擇刪除末尾 x 個元素

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

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

  • 棧為空
  • 新的棧頂元素不大於當前數字
  • 已經刪除了 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();
    }
}

相關文章