程式設計師面試金典 - 面試題 03.05. 棧排序
阿新 • 來源:網路 • 發佈:2021-02-19
題目難度: 中等
今天繼續更新程式設計師面試金典系列, 大家在公眾號 演算法精選 裡回覆 面試金典 就能看到該系列當前連載的所有文章了, 記得關注哦~
題目描述
棧排序。 編寫程式,對棧進行排序使最小元素位於棧頂。最多隻能使用一個其他的臨時棧存放資料,但不得將元素複製到別的資料結構(如陣列)中。該棧支援如下操作: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
思路
- 根據題目描述, 我們可以採用插入排序的思路直接模擬整個過程
- 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
大家可以在下面這些地方找到我~
