算法-棧隊列堆
簡介:算法篇-棧隊列堆
不敢高聲語,恐驚天上人。
一、用兩個棧實現隊列
1、題目描述
用兩個棧來實現一個隊列,完成隊列的 Push 和 Pop 操作。
2、解題思路
in 棧用來處理入棧(push)操作,out 棧用來處理出棧(pop)操作。一個元素進入 in 棧之后,出棧的順序被反轉。當元素要出棧時,需要先進入 out 棧,此時元素出棧順序再一次被反轉,因此出棧順序就和最開始入棧順序是相同的,先進入的元素先退出,這就是隊列的順序。
3、代碼示例

1 import java.util.Stack; 2
3 public class Solution { 4 Stack<Integer> stack1 = new Stack<Integer>(); 5 Stack<Integer> stack2 = new Stack<Integer>(); 6
7 public void push(int node) { 8 stack1.push(node); 9 } 10
11 public int pop() { 12 if(stack2.empty()){ 13 while(!stack1.empty()){ 14 stack2.push(stack1.pop()); 15 } 16 } 17 return stack2.pop(); 18 } 19 }
二、包含min函數的棧
1、題目描述
實現一個包含 min() 函數的棧,該方法返回當前棧中最小的值。
2、解題思路
使用一個額外的 minStack,棧頂元素為當前棧中最小的值。在對棧進行 push 入棧和 pop 出棧操作時,同樣需要對 minStack 進行入棧出棧操作,從而使 minStack 棧頂元素一直為當前棧中最小的值。在進行 push 操作時,需要比較入棧元素和當前棧中最小值,將值較小的元素 push 到 minStack 中。
3、代碼示例

1 import java.util.Stack; 2
3 public class Solution { 4 // 創建兩個棧:一個用於存放所有元素,一個用於存放每添加一個新元素之后的最小元素
5 Stack<Integer> total = new Stack<>(); 6 Stack<Integer> mininum = new Stack<>(); 7
8 // 若棧為空,則同時往兩個棧壓入元素,反之像mininum壓入元素-此時需要比較當前元素與棧頂元素的大小
9 public void push(int node) { 10 total.push(node); 11 if(mininum.isEmpty()){ 12 mininum.push(node); 13 }else{ 14 if(node < mininum.peek()){ 15 mininum.push(node); 16 }else{ 17 mininum.push(mininum.peek()); 18 } 19 } 20 } 21
22 // 兩個棧的元素同時出棧
23 public void pop() { 24 total.pop(); 25 mininum.pop(); 26 } 27
28 // 返回total 的棧頂元素
29 public int top() { 30 return total.peek(); 31 } 32
33 // 返回mininum 的棧頂元素
34 public int min() { 35 return mininum.peek(); 36 } 37 }
三、棧的壓入彈出序列
1、題目描述
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否為該棧的彈出順序。假設壓入棧的所有數字均不相等。
例如序列 1,2,3,4,5 是某棧的壓入順序,序列 4,5,3,2,1 是該壓棧序列對應的一個彈出序列,但 4,3,5,1,2 就不可能是該壓棧序列的彈出序列。
2、解題思路
使用一個棧來模擬壓入彈出操作。每次入棧一個元素后,都要判斷一下棧頂元素是不是當前出棧序列 popSequence 的第一個元素,如果是的話則執行出棧操作並將 popSequence 往后移一位,繼續進行判斷。
3、代碼示例

1 import java.util.ArrayList; 2 import java.util.*; 3
4 public class Solution { 5 public boolean IsPopOrder(int [] pushA,int [] popA) { 6 Stack<Integer> stack = new Stack<Integer>(); 7 int i = 0; 8 for(int temp : pushA){ 9 stack.push(temp); 10 // peek() 返回棧頂元素
11 while((!stack.isEmpty()) && stack.peek() == popA[i]){ 12 stack.pop(); // 移除棧頂元素
13 i++; 14 } 15 } 16 return stack.isEmpty(); 17 } 18 }
四、最小的K個數
1、題目描述
給定一個數組,找出其中最小的K個數。例如數組元素是4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4。如果K>數組的長度,那么返回一個空的數組
2、解題思路
- 復雜度:O(NlogK) + O(K)
- 特別適合處理海量數據
維護一個大小為 K 的最小堆過程如下:使用大頂堆。在添加一個元素之后,如果大頂堆的大小大於 K,那么將大頂堆的堆頂元素去除,也就是將當前堆中值最大的元素去除,從而使得留在堆中的元素都比被去除的元素來得小。
應該使用大頂堆來維護最小堆,而不能直接創建一個小頂堆並設置一個大小,企圖讓小頂堆中的元素都是最小元素。
Java 的 PriorityQueue 實現了堆的能力,PriorityQueue 默認是小頂堆,可以在在初始化時使用 Lambda 表達式 (o1, o2) -> o2 - o1 來實現大頂堆。其它語言也有類似的堆數據結構。
3、代碼示例

1 import java.util.*; 2
3 public class Solution { 4
5 public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { 6 ArrayList<Integer> list = new ArrayList<Integer>(); 7 int length = input.length; 8 if(k > length || k < 0){ 9 return list; 10 } 11
12 for(int i = 0; i < length - 1; i++){ 13 for(int j = 0; j < length - i - 1; j++){ 14 if(input[j] > input[j+1]){ 15 int temp = input[j]; // 簡單排序
16 input[j] = input[j+1]; 17 input[j+1] = temp; 18 } 19 } 20 } 21
22 for(int i = 0; i < k; i++){ 23 list.add(input[i]); // 取最小的前K個數
24 } 25
26 return list; 27 } 28 }
五、數據流在的中位數
1、題目描述
如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那么中位數就是所有數值排序之后位於中間的數值。如果從數據流中讀出偶數個數值,那么中位數就是所有數值排序之后中間兩個數的平均值。
2、代碼示例

1 import java.util.*; 2
3 public class Solution { 4
5 ArrayList<Integer> array = new ArrayList<>(); 6
7 public void Insert(Integer num) { 8 array.add(num); 9 } 10
11 public Double GetMedian() { 12 Collections.sort(array); // 排序
13 int index = array.size() / 2; 14 if(array.size() % 2 != 0){ // 奇數直接取值
15 return (double)array.get(index); 16 } 17 // 偶數取平均值
18 return ((double)(array.get(index - 1)) + (double)array.get(index)) / 2; 19 } 20
21
22 }
六、字符流中第一個不重復的字符
1、題目描述
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符 "go" 時,第一個只出現一次的字符是 "g"。當從該字符流中讀出前六個字符“google" 時,第一個只出現一次的字符是 "l"。
2、解題思路
使用統計數組來統計每個字符出現的次數,本題涉及到的字符為都為 ASCII 碼,因此使用一個大小為 128 的整型數組就能完成次數統計任務。
使用隊列來存儲到達的字符,並在每次有新的字符從字符流到達時移除隊列頭部那些出現次數不再是一次的元素。因為隊列是先進先出順序,因此隊列頭部的元素為第一次只出現一次的字符。
3、代碼示例

1 public class Solution { 2 //Insert one char from stringstream
3 String stream = ""; 4 int [] cache = new int[256]; 5 public void Insert(char ch) { 6 stream += ch; 7 cache[ch]++; 8 } 9 //return the first appearence once char in current stringstream
10 public char FirstAppearingOnce() { 11 for(int i = 0; i < stream.length(); i++){ 12 if(cache[stream.charAt(i)] == 1){ 13 return stream.charAt(i); 14 } 15 } 16 return '#'; 17 } 18 }
七、滑動窗口的最大值
1、題目描述
給定一個數組和滑動窗口的大小,找出所有滑動窗口里數值的最大值。
例如,如果輸入數組 {2, 3, 4, 2, 6, 2, 5, 1} 及滑動窗口的大小 3,那么一共存在 6 個滑動窗口,他們的最大值分別為 {4, 4, 6, 6, 6, 5}。
2、解題思路
維護一個大小為窗口大小的大頂堆,頂堆元素則為當前窗口的最大值。
假設窗口的大小為 M,數組的長度為 N。在窗口向右移動時,需要先在堆中刪除離開窗口的元素,並將新到達的元素添加到堆中,這兩個操作的時間復雜度都為 log2M,因此算法的時間復雜度為 O(Nlog2M),空間復雜度為 O(M)。
3、代碼示例

1 import java.util.*; 2
3 public class Solution { 4 public ArrayList<Integer> maxInWindows(int [] num, int size) { 5 ArrayList<Integer> result = new ArrayList<>(); 6 if(num.length < size || 0 == size){ 7 return result; 8 } 9 // deque容器為一個給定類型的元素進行線性處理,像向量一樣,它能夠快速地隨機訪問任一個元素,並且能夠高效地插入和刪除容器的尾部元素
10 Deque<Integer> quque = new LinkedList<>(); 11 // 初始化雙端隊列元素
12 for(int i = 0; i < size - 1; i++){ 13 while(!quque.isEmpty() && num[quque.peekLast()] < num[i]){ 14 quque.pollLast(); 15 } 16 quque.add(i); 17 } 18
19 for(int i = size - 1; i < num.length; i++){ 20 // 檢查隊列內容是否合法,判斷隊列頭部元素是否位於窗口內
21 while(!quque.isEmpty() && i - quque.peekFirst() + 1 > size){ 22 quque.pollFirst(); 23 } 24 // 從隊列尾部移除所有比當前值小的元素
25 while(!quque.isEmpty() && num[quque.peekLast()] < num[i]){ 26 quque.pollLast(); 27 } 28 quque.offerLast(i); 29 result.add(num[quque.peekFirst()]); 30 } 31 return result; 32 } 33 }
不敢高聲語
恐驚天上人