1. 程式人生 >實用技巧 >其它 >程式設計師面試金典 - 面試題 03.05. 棧排序

程式設計師面試金典 - 面試題 03.05. 棧排序


技術標籤:Leetcode資料結構演算法python面試

題目難度: 中等

原題連結

今天繼續更新程式設計師面試金典系列, 大家在公眾號 演算法精選 裡回覆 面試金典 就能看到該系列當前連載的所有文章了, 記得關注哦~

題目描述

棧排序。 編寫程式,對棧進行排序使最小元素位於棧頂。最多隻能使用一個其他的臨時棧存放資料,但不得將元素複製到別的資料結構(如陣列)中。該棧支援如下操作:push、pop、peek 和 isEmpty。當棧為空時,peek 返回 -1。

示例 1:

  • 輸入:
    [“SortedStack”, “push”, “push”, “peek”, “pop”, “peek”]
    [[], [1], [2], [], [], []]
  • 輸出:
    [null,null,null,1,null,2]

示例 2:

  • 輸入:
    [“SortedStack”, “pop”, “pop”, “push”, “pop”, “isEmpty”]
    [[], [], [], [1], [], []]
  • 輸出:
    [null,null,null,null,null,true]

說明:

棧中的元素數目在[0, 5000]範圍內。

題目思考

  1. 如何利用輔助棧來做到有序, 如何優化?

解決方案

方案 1

思路

  • 根據題目描述, 我們可以採用插入排序的思路直接模擬整個過程
  • push 的時候使用輔助棧來保證 val 插入到合適的位置, 主棧保持棧頂到棧頂遞減的順序
  • 這樣 pop 和 push 的時候就可以直接拿到棧頂, 它就是最小值

複雜度

  • 時間複雜度
    • peek 和 pop 顯然是 O(1)
    • push 需要 O(N)
  • 空間複雜度
    • 使用了兩個棧存 N 個元素

程式碼

class SortedStack:
    # 方法1: 模擬, 插入排序思路
    # push的時候使用輔助棧來保證val插入到合適的位置, 主棧保持棧頂到棧頂遞減的順序
    def __init__(self):
        self.stack = []

    def push(self, val: int) -> None:
        temp = []
        while self.stack and self.stack[-1] < val:
            # 將當前棧中小於val的部分先臨時存到輔助棧中
            temp.append(self.stack.pop())
        # 找到合適位置了, 將當前val追加到棧頂
        self.stack.append(val)
        while temp:
            # 再將輔助棧的元素倒回來, 保證仍有序
            self.stack.append(temp.pop())

    def pop(self) -> None:
        if self.isEmpty():
            return
        # 主棧有序, 所以直接pop棧頂即可
        self.stack.pop()

    def peek(self) -> int:
        if self.isEmpty():
            return -1
        # 主棧有序, 所以棧頂即為所求
        return self.stack[-1]

    def isEmpty(self) -> bool:
        return not self.stack

方案 2

思路

  • 方案 1 在每次 push 時都需要從輔助棧和主棧中來回倒資料, 特別是連續 push 多個較大值時, 效率很低, 因為每次都需要將較大值到棧頂的部分倒入輔助棧, 最後再倒回來, 如何優化這部分操作呢?
  • 我們可以持久化儲存輔助棧, 避免連續 push 多個值時的來回倒入
  • 輔助棧從棧底到棧頂單調遞增, 而主棧從棧底到棧頂單調遞減, 且輔助棧元素都小於主棧元素
  • 這樣 push 的時候只需要根據兩個棧棧頂的值, 來回倒有限次後就找到了合適的插入位置, 最關鍵的是連續 push 多個值的時候, 也不需要每次都將該值到最小值之間的部分倒入到輔助棧再倒回來
  • 而 pop/peek 的時候則必須要將輔助棧的元素倒入主棧中, 使得主棧棧頂是最小值. 這樣也不會降低連續 pop/peek 的效能: 因為第一次 pop/peek 後的主棧就有序了, 之後的 pop/peek 直接拿棧頂即可, 不需要再次倒入

複雜度

  • 時間複雜度
    • peek 和 pop 雖然有迴圈, 但均攤下來仍是 O(1), 因為連續的 peek/pop 只有第一次操作需要迴圈
    • push 需要 O(N), 但對連續的 push 有所優化
  • 空間複雜度
    • 使用了兩個棧存 N 個元素

程式碼

class SortedStack:
    # 方法2: 持久化儲存輔助棧, 避免連續push多個值時的來回倒入
    def __init__(self):
        self.stack = []
        self.temp = []

    def push(self, val: int) -> None:
        while self.stack and self.stack[-1] < val:
            # 主棧棧頂小於val, 需要將主棧棧頂元素倒入輔助棧中
            self.temp.append(self.stack.pop())
        while self.temp and self.temp[-1] > val:
            # 輔助棧棧頂大於val, 需要將輔助棧棧頂元素倒入主棧中
            self.stack.append(self.temp.pop())
        # 找到合適位置了, 插入主棧棧頂
        self.stack.append(val)

    def pop(self) -> None:
        if self.isEmpty():
            return
        while self.temp:
            # 將當前輔助棧元素倒入主棧中
            self.stack.append(self.temp.pop())
        # 此時主棧有序, 所以直接pop棧頂即可
        self.stack.pop()

    def peek(self) -> int:
        if self.isEmpty():
            return -1
        while self.temp:
            # 將當前輔助棧元素倒入主棧中
            self.stack.append(self.temp.pop())
        # 此時主棧有序, 所以棧頂即為所求
        return self.stack[-1]

    def isEmpty(self) -> bool:
        return not self.stack and not self.temp

大家可以在下面這些地方找到我~