面試題30 包含min函數的棧
題目描述
定義棧的數據結構,請在該類型中實現一個能夠得到棧最小元素的min函數。在該棧中,調動min、push、及pop的時間複雜度都是O(1)。
爲了保證當我們彈出當前最小元素後,下一個最小元素也能夠立即得出。因此我們可以把每次的最小元素放到另一個輔助棧中。以下列例子爲例:
首先往空棧裏壓入數字3,顯然3是當前最小值,把3也壓入輔助棧中。接下來壓入4,發現4大於之前的最小值,因此把4壓入數據棧後,往輔助棧裏繼續壓入最小值3。第三步,往數據棧裏壓入2,由於2小於之前的最小值3,所以把2壓入數據棧後,需要在輔助棧中壓入數字2,同樣壓入數字1的時候,也要更新最小值,並把1壓入輔助棧。
從上表可以看出,如果每次都把最小元素壓入輔助棧,那麼就能保證輔助棧的棧頂一直都是最小元素。當最小元素從數據棧內被彈出時,輔助棧的新棧頂元素就是下一個最小值。當第五步彈出數據棧的1之後,我們會把輔助棧的棧頂也彈出,這樣,輔助棧的棧頂元素2就是新的最小元素。接下來繼續彈出數據棧和輔助棧的棧頂元素,可以發現,每次彈出後,輔助棧的棧頂元素都是棧裏的最小元素,這說明思路沒有問題。
以下爲測試代碼:
using System;
using System.Collections.Generic;
namespace 包含min函數的棧
{
class Program
{
static void Main(string[] args)
{
Solution s = new Solution();
s.Push(3);
s.Push(4);
s.Push(2);
s.Push(1);
s.PrintStack();
Console.WriteLine("Top:"+s.Top());
Console.WriteLine("Min:" + s.Min());
s.Pop();
s.Push(0);
s.PrintStack();
s.Pop();
s.Pop();
s.Pop();
s.PrintStack();
}
}
class Solution
{
public Stack<int> m_data = new Stack<int>(); //數據棧
private Stack<int> m_min = new Stack<int>(); //輔助棧
public void Push(int node)
{
m_data.Push(node);
//判斷壓入數據是否小於輔助棧棧頂元素,如果小於,則把當前元素壓入棧中,如果大於,則壓入輔助棧的棧頂元素
if (m_min.Count == 0 || node < m_min.Peek())
m_min.Push(node);
else
m_min.Push(m_min.Peek());
}
public void Pop()
{
if (m_data.Count > 0 && m_min.Count > 0)
{
m_data.Pop();
m_min.Pop();
}
}
public int Top()
{
if (m_data.Count > 0 && m_min.Count > 0)
return m_data.Peek();
return -1;
}
public int Min()
{
if (m_data.Count > 0 && m_min.Count > 0)
return m_min.Peek();
return -1;
}
public void PrintStack()
{
Console.Write("數據棧:");
foreach (int item in m_data)
Console.Write(item + "\t");
Console.WriteLine();
Console.Write("輔助棧:");
foreach (int item in m_min)
Console.Write(item + "\t");
Console.WriteLine();
}
}
}
棧的壓入、彈出序列
問題描述
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)
解決這個問題最直觀的方法就是建立一個輔助棧,把輸入的第一個序列的數字依次壓入輔助棧中,並按照第二個序列的順序依次從該棧彈出數字。以彈出{4,5,3,2,1}爲例分析壓棧和彈出的過程。
先壓入1,2,3,4,其中4位於棧頂,把4彈出棧後,剩下的3個數字是1,2,3。接下來希望被彈出的數字是5,由於5不是棧頂數字,因此我們接着在第一個序列中把4以後的數字壓入輔助棧中,直到壓入的數字爲5。這時,5位於棧頂,就可以被彈出了,接下來希望被彈出的三個數字分別是3,2,1。由於每次操作前它們都位於棧頂,因此直接彈出即可。下面表爲壓入彈出的總過程:
接下來再分析彈出序列爲{4,3,5,1,2}。
第一個彈出的數字4的情況和上述一樣,把4彈出後,3位於棧頂,可以直接彈出。接下來希望彈出的數字是5,由於5不在棧內,所以把沒有壓入棧的數字壓入輔助棧中,直到5爲棧頂。彈出5,此時棧裏還剩1,2,其中2位於棧頂。由於接下來需要彈出的數字是1,但1不在棧頂,我們需要把尚未壓入棧的數字壓入棧中,但是此時壓棧序列中的所有數字都已經壓入棧了,所以該序列不是序列{1,2,3,4,5}對應的彈出序列。下表爲壓入彈出的總過程:
總結上述入棧、出棧的過程,我們可以找到判斷一個序列是不是棧的彈出序列的規律:如果下一個彈出的數字剛好是棧頂數字,那麼直接彈出;如果彈出的數字不在棧頂,則把壓棧序列中還沒有入棧的數字壓入輔助棧,直到把下一個需要彈出的數字壓入棧頂爲止;如果所有數字都壓入棧後依然沒有找到下一個數字,那麼該序列則不可能是一個彈出序列。
以下爲該題的參考代碼:
using System;
using System.Collections.Generic;
namespace 棧的壓入_彈出序列
{
class Program
{
static void Main(string[] args)
{
int[] arr1 = new int[5] { 1, 2, 3, 4, 5 };
int[] arr2 = new int[5] { 5, 4, 3, 2, 1 };
int[] arr3 = new int[5] { 4, 3, 5, 1, 2 };
Solution s = new Solution();
Console.WriteLine("PushArray: 1,2,3,4,5\nPopArray:5,4,3,2,1");
Console.WriteLine(s.IsPopOrder(arr1, arr2));
Console.WriteLine("PushArray: 1,2,3,4,5\nPopArray:4,3,5,1,2");
Console.WriteLine(s.IsPopOrder(arr1, arr3));
}
}
class Solution
{
public bool IsPopOrder(int[] pushV, int[] popV)
{
//如果傳入的兩個序列有一個爲空,則不可能存在彈出序列
if (pushV == null || popV == null)
return false;
//bPossible用於儲存是否爲彈出序列的布爾值
bool bPossible = false;
//pushIndex爲壓入序列的索引, popIndex彈出序列的索引
int pushIndex = 0, popIndex = 0;
//如果彈出序列的索引在彈出序列長度之內,則進行檢測
if (popIndex < popV.Length)
{
//用於儲存彈出數字的輔助棧
Stack<int> stack = new Stack<int>();
//如果彈出序列還有數字沒有進入輔助棧彈出,那麼繼續循環
while (popIndex < popV.Length)
{
//如果棧爲空或者棧頂元素不是彈出序列需要彈出的值,則把尚未壓入輔助棧的元素壓入輔助棧中
while (stack.Count == 0 || stack.Peek() != popV[popIndex])
{
//如果壓入序列已經沒有元素可以壓入輔助棧了,則彈出
if (pushIndex == pushV.Length)
break;
//把尚未壓入輔助棧的元素壓入輔助棧,並將壓入序列的索引往後移一位
stack.Push(pushV[pushIndex]);
pushIndex++;
}
//如果在循環中把可以壓入的元素都壓入棧卻依然無法讓輔助棧棧頂元素等於彈出序列的彈出元素時,退出循環,返回false
if (stack.Peek() != popV[popIndex])
break;
//如果找到了與彈出序列需要彈出的值相等的棧頂元素,則彈出棧頂元素,彈出序列索引值往後移
stack.Pop();
popIndex++;
}
//如果輔助棧所有元素都已經被彈出且彈出序列已經沒有數字需要彈出,返回true
if (popIndex == popV.Length && stack.Count == 0)
bPossible = true;
}
return bPossible;
}
}
}