轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/6857537.html
一:問題概述
棧與隊列的相關算法題,一般都是基於對棧、隊列基本性質的熟練掌握的前提下,如何巧妙地組合、包裝,以達到某種原來數據類型所沒有的性質。比如:設計出一種能getMin()獲取棧中最小值的棧、利用棧實現隊列等等。問題的本意為考察棧、隊列的基本性質與基本操作的靈活使用,所以解題思路也是從這邊出發:怎么利用現有的數據結構以及其基本性質、操作,去組合、包裝,實現題目要求?
二:棧、隊的基本性質與操作
棧:后進先出。Java中Stack類實現了棧,基本操作有:empty()判斷是否為空、peek()查看棧頂、pop()彈出棧頂、push()入棧。
隊列:先進先出。Java中用LinkedList實現了隊列(LinkedList也實現了List接口和deque接口,所以LinkedList中包含了list、單向隊列、雙向隊列的操作),單向隊列的基本操作有:peek()查看隊首、poll()彈出隊首、offer()加入隊尾、size()返回大小。
三:設計一個可查詢最值的棧
我們知道,基本的棧是“后進先出”的,棧中的最值沒有固定的位置,所以想要靠位置記錄來查找是行不通的;其次,棧中元素隨着push/pop操作,最值是會變化的,怎么隨着push/pop操作動態修改最值呢?
下面是例題:
定義一種棧的數據結構,在該類型中實現一個能夠得到棧最小元素的min函數。
public class Solution { //兩個棧,一個記錄數據,一個記錄棧中每層數據對應着的當前棧中最小值 Stack data=new Stack(); Stack min=new Stack(); int curr_min=Integer.MAX_VALUE; //入棧 public void push(int node) { data.push(node); //min棧記錄當前入棧元素對應的當前棧中最小值是誰 if(node<=curr_min){ min.push(node); curr_min=node;//更新最小值 }else{ min.push(curr_min); } } //彈出元素,兩個棧同步彈出 public void pop() { data.pop(); min.pop(); } public int top() { return (Integer)data.peek(); } public int min() { return (Integer)min.peek(); } }
四:用兩個棧,實現隊列
棧是“后進先出”的,所以一般用於實現“倒序”問題。而隊列是“先進先出”的,用棧實現隊列,由“負負得正”即可知道,需要用兩個棧。第一個棧接收數據,第二個棧負責把第一個棧的數據順序調整回來,使先進第一個棧的,先從第二個棧彈出。題目唯一要注意的是:不是單純地把第一個棧的數據轉移到第二個棧,轉移的時機很重要——第二個棧為空時,才把第一個棧的元素全部轉移過來。
public int[] twoStack(int[] ope, int n) { Stack in=new Stack(); Stack out=new Stack(); ArrayList<Integer> pops=new ArrayList<Integer>(); for(int i:ope){ if(i>0){ in.push(i); }else{ //雙棧實現隊列的唯一重點:有出隊命令時,首先應該檢查out棧有無元素,有的話直接out棧彈出即可; //若out棧為空,才把in棧中元素全部彈出入out棧,調換了位置,然后彈出out棧棧頂 if(out.empty()){ while(!in.empty()){ int val=(Integer)in.pop(); out.push(val); } } int valout=(Integer)out.pop(); pops.add(valout); } } int[] res=new int[pops.size()]; for(int i=0;i<pops.size();++i){ res[i]=pops.get(i); } return res; }
五:實現棧的逆序
在不借助額外的數據結構的前提下,把當前棧中的存儲的數據逆序存放。即:原先位於棧頂的,放到棧底......
思路:
這道題,如果可以借助額外的數據結構,則很容易實現,比如:使用第二個棧依次接收第一個棧中彈出的數據即可。但是題目要求不能借助額外的數據結構,這時我們就不得不找替代品——除了棧之外,還有什么擁有“后進先出”的特性呢?沒錯,那就是遞歸!其實遞歸函數的執行,歸根結底還是使用了棧的,那就是函數棧。所以,這里我們就在遞歸函數中操作原有棧,利用遞歸函數的return順序實現數據倒序。
解題偶得:遞歸通常在參數中傳遞被各層操作的數據、也在參數中傳遞控制遞歸深度的變量。遞歸返回數據有兩種方法:遞歸函數的返回值、通過操作參數中的數組來攜帶數據。
遞歸思路整理:
recur(){ 遞歸下層前代碼:按遞歸順序執行,同時,也是遞歸終點返回數據的地方; recur();//遞歸指令:從這里進入下層執行,等待下層執行完畢並返回 遞歸后代碼:獲取下層執行完畢並返回的數據,繼續完成該層的所有操作。按遞歸順序的逆序執行 }
代碼:
//對每一層:先取出棧中底部元素,並且不改變上面的元素 public int getBottom(Stack<Integer> data){ int curr=(Integer)data.pop(); if(data.empty()){//如果取出后,棧空了,說明這是棧底元素,返回上層 return curr; } int bottom=getBottom(data);//獲取下層返回的棧底元素 data.push(curr);//把當前層元素重新入棧 return bottom;//把獲取到的棧底元素往上傳遞 } //實現逆序 public void recur(Stack<Integer> data,int n){ if(n==0){ return;//控制遞歸終點 } int bottom=getBottom(data);//按遞歸順序獲取棧底 recur(data,n-1); data.push(bottom);//按遞歸逆序把每一層獲取到的棧底入棧,從而實現原先的棧底最后入棧,成為棧頂 }
六:棧排序問題
給定一個棧,棧中有數據。要求最多只能額外借助一個棧,不能用其他數據結構復制數據。實現對棧中數據排序。
思路:使用一個輔助棧來協助排序,其實就是一個不斷彈出、比較、放回、入棧的過程。(類比漢諾塔)
public ArrayList<Integer> twoStacksSort(int[] numbers) { //由於Stack的peek()沒元素則拋出異常,而LinkedList中的peekFirst()無元素則返回Null。所以用LinkedList模擬棧 LinkedList<Integer> A=new LinkedList<Integer>(); LinkedList<Integer> B=new LinkedList<Integer>(); //對需要處理的數據元素入棧 for(int i=numbers.length-1;i>=0;--i){ A.push(numbers[i]); } //雙棧排序 while(A.size()>0){ Integer curr=A.pop(); //linkedlist沒有empty()方法,所以只能用size()與0比較判斷是否為空 if(B.size()==0){ B.push(curr); continue; }else{ //如果A彈出的元素大於B棧頂,則需要把它放在當前B棧頂的下面某處 while(B.peekFirst()!=null && curr>B.peekFirst()){ //所以把B棧頂彈出,放回A,使得B棧中第二個元素成為棧頂,用於下一循環時比較 Integer back=B.pop(); //把B彈回的元素放入A棧,等下重新彈出入B棧 A.push(back); } //B棧中小於當前A彈出元素的已回到A棧,這時把curr入B棧,就是合適的位置。B棧從下往上是大——>小。 B.push(curr); } } //上面循環結束后,B棧中元素已經是有序的:從下往上==從大到小 while(B.size()>0){ A.push((Integer)B.pop()); } ArrayList<Integer> res=new ArrayList<Integer>(); while(A.size()>0){ res.add((Integer)A.pop()); } return res; }