面试题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;
}
}
}