栈与队列是常用的数据结构,其中栈提供先进后出,队列提供先进先出,这样的数据结构可以解决一些需要顺序解决的问题。在java中实现底层是不一样的原因是栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
在Java中,队列和栈是两种常见的数据结构,它们分别用于不同的场景,而它们的实现通常基于以下几种容器:
队列(Queue)的容器实现:
LinkedList:
java.util.LinkedList
是一个双向链表,支持在两端进行元素的添加和删除,因此适用于实现队列。
- 可以使用
offer()
在队尾添加元素,使用 poll()
移除并返回队头元素。
1 2 3 4
| Queue<Integer> queue = new LinkedList<>(); queue.offer(1); queue.offer(2); int frontElement = queue.poll();
|
ArrayDeque:
java.util.ArrayDeque
是一个基于数组的双端队列,同样适用于实现队列。
- 可以使用
offer()
在队尾添加元素,使用 poll()
移除并返回队头元素。
1 2 3 4
| Queue<Integer> queue = new ArrayDeque<>(); queue.offer(1); queue.offer(2); int frontElement = queue.poll();
|
PriorityQueue:
java.util.PriorityQueue
是一个基于优先级堆的无界优先级队列,可以实现具有优先级的队列。
- 元素按照优先级顺序被移除。
1 2 3 4
| Queue<Integer> priorityQueue = new PriorityQueue<>(); priorityQueue.offer(3); priorityQueue.offer(1); int highestPriority = priorityQueue.poll();
|
栈(Stack)的容器实现:
LinkedList:
java.util.LinkedList
可以用作栈的实现,因为它是一个双向链表,支持在两端进行元素的添加和删除。
- 可以使用
push()
入栈,pop()
出栈,peek()
查看栈顶元素。
1 2 3 4
| Deque<Integer> stack = new LinkedList<>(); stack.push(1); stack.push(2); int topElement = stack.pop();
|
ArrayDeque:
java.util.ArrayDeque
同样可以用作栈的实现,支持在两端进行元素的添加和删除。
- 可以使用
push()
入栈,pop()
出栈,peek()
查看栈顶元素。
1 2 3 4
| Deque<Integer> stack = new ArrayDeque<>(); stack.push(1); stack.push(2); int topElement = stack.pop();
|
注意:在现代Java编程中,推荐使用 Deque
接口来代替 Stack
类,因为 Deque
提供了更灵活的双端队列操作,并且 Stack
类是一个遗留类。
232.用栈实现队列
力扣题目链接
如果要用栈实现队列,那么就要保证先进先出,所以就必须用两个栈,一个负责弹出到底部一个负载接收其他变量。
1 2 3 4 5 6 7 8 9 10 11 12
| stackIn = new Stack<>(); stackOut = new Stack<>(); public int pop() { dumpstackIn(); return stackOut.pop(); } private void dumpstackIn(){ if (!stackOut.isEmpty()) return; while (!stackIn.isEmpty()){ stackOut.push(stackIn.pop()); } }
|
225. 用队列实现栈
力扣题目链接
这个有多种实现思路,可以在放入队列时就保证出栈顺序。也可以在出队列时出最后一个,还可以使用一个队列循环size长度然后把出队的元素重新入队,直到找到最后一个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| class MyStack {
Queue<Integer> queue1; Queue<Integer> queue2;
public MyStack() { queue1 = new LinkedList<>(); queue2 = new LinkedList<>(); } public void push(int x) { queue2.offer(x); while (!queue1.isEmpty()){ queue2.offer(queue1.poll()); } Queue<Integer> queueTemp; queueTemp = queue1; queue1 = queue2; queue2 = queueTemp; } Deque<Integer> que1; Deque<Integer> que2; public int pop() { int size = que1.size(); size--; while (size-- > 0) { que2.addLast(que1.peekFirst()); que1.pollFirst(); }
int res = que1.pollFirst(); que1 = que2; que2 = new ArrayDeque<>(); return res; } class MyStack { Deque<Integer> que1; public int pop() { int size = que1.size(); size--; while (size-- > 0) { que1.addLast(que1.peekFirst()); que1.pollFirst(); }
int res = que1.pollFirst(); return res; }
|
20. 有效的括号
力扣题目链接
两种情况比较遇见左括号就入栈右括号和遇到左括号就入栈左括号对比,可以看到如果采用遇到左括号就入栈右括号就不用进行匹配了可以直接相等比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| for (int i = 0; i < s.length(); i++) { char currentChar = s.charAt(i);
if (currentChar == '(' || currentChar == '[' || currentChar == '{') { stack.push(currentChar); } else if (currentChar == ')' || currentChar == ']' || currentChar == '}') { if (stack.isEmpty() || (currentChar == ')' && stack.peek() != '(') || (currentChar == ']' && stack.peek() != '[') || (currentChar == '}' && stack.peek() != '{')) { return false; } stack.pop(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| for (int i = 0; i < s.length(); i++) { ch = s.charAt(i); if (ch == '(') { deque.push(')'); }else if (ch == '{') { deque.push('}'); }else if (ch == '[') { deque.push(']'); } else if (deque.isEmpty() || deque.peek() != ch) { return false; }else { deque.pop(); } } return deque.isEmpty(); }
|
1047. 删除字符串中的所有相邻重复项
力扣题目链接
很简单的逻辑如果,某个元素和栈顶元素一样就出栈栈顶,不一样就入栈。这里简单介绍三个方法
方法1:列表
链接结构可能是最糟糕的结构,每个元素上都有缓存未命中而进行迭代。最重要的是,它们消耗更多的内存。
如果您需要添加/删除两端,ArrayDeque 明显优于 LinkedList。对于循环队列来说,随机访问每个元素也是 O(1)。
链表唯一更好的操作是在迭代期间删除当前元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Solution { public String removeDuplicates(String S) { ArrayDeque<Character> deque = new ArrayDeque<>(); char ch; for (int i = 0; i < S.length(); i++) { ch = S.charAt(i); if (deque.isEmpty() || deque.peek() != ch) { deque.push(ch); } else { deque.pop(); } } String str = ""; while (!deque.isEmpty()) { str = deque.pop() + str; } return str; } }
|
方法2双指针快指针指向需要匹配元素 ,慢指针指向已经匹配元素,其中的slow–和slow++是灵魂,slow–代表如果匹配就删除数组中的slow,slow++不匹配就向前移动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| lass Solution { public String removeDuplicates(String s) { char[] ch = s.toCharArray(); int fast = 0; int slow = 0; while(fast < s.length()){ ch[slow] = ch[fast]; if(slow > 0 && ch[slow] == ch[slow - 1]){ slow--; }else{ slow++; } fast++; } return new String(ch,0,slow); } }
|
239. 滑动窗口最大值
力扣题目链接
需要返回滑动窗口的最大值,这可以使用一个单调队列
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
class MyQueue { Deque<Integer> deque = new LinkedList<>(); void poll(int val) { if (!deque.isEmpty() && val == deque.peek()) { deque.poll(); } } void add(int val) { while (!deque.isEmpty() && val > deque.getLast()) { deque.removeLast(); } deque.add(val); } int peek() { return deque.peek(); } }
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { if (nums.length == 1) { return nums; } int len = nums.length - k + 1; int[] res = new int[len]; int num = 0; MyQueue myQueue = new MyQueue(); for (int i = 0; i < k; i++) { myQueue.add(nums[i]); } res[num++] = myQueue.peek(); for (int i = k; i < nums.length; i++) { myQueue.poll(nums[i - k]); myQueue.add(nums[i]); res[num++] = myQueue.peek(); } return res; } }
|
347.前 K 个高频元素(review)
力扣题目链接
什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
class Solution { public int[] topKFrequent1(int[] nums, int k) { Map<Integer,Integer> map = new HashMap<>(); for(int num:nums){ map.put(num,map.getOrDefault(num,0)+1); } PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]); for(Map.Entry<Integer,Integer> entry:map.entrySet()){ pq.add(new int[]{entry.getKey(),entry.getValue()}); } int[] ans = new int[k]; for(int i=0;i<k;i++){ ans[i] = pq.poll()[0]; } return ans; } public int[] topKFrequent2(int[] nums, int k) { Map<Integer,Integer> map = new HashMap<>(); for(int num:nums){ map.put(num,map.getOrDefault(num,0)+1); } PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]); for(Map.Entry<Integer,Integer> entry:map.entrySet()){ if(pq.size()<k){ pq.add(new int[]{entry.getKey(),entry.getValue()}); }else{ if(entry.getValue()>pq.peek()[1]){ pq.poll(); pq.add(new int[]{entry.getKey(),entry.getValue()}); } } } int[] ans = new int[k]; for(int i=k-1;i>=0;i--){ ans[i] = pq.poll()[0]; } return ans; } }
|