演算法-棧佇列堆
簡介:演算法篇-棧佇列堆
不敢高聲語,恐驚天上人。
一、用兩個棧實現佇列
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 }
二、包含main函式的棧
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 }
四、包含min函式的棧
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 }
不敢高聲語
恐驚天上人