算法-栈队列堆
简介:算法篇-栈队列堆
不敢高声语,恐惊天上人。
一、用两个栈实现队列
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 }
不敢高声语
恐惊天上人