数据结构与算法(Ⅱ): 栈、队列、优先队列、双端队列
栈 Stack
栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端即栈的顶端(top),对栈的基本操作有push(进栈)、pop(出栈)。
先进后出(FILO, first-in-last-out), 查询O(n)
Java Stack
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractList<E>
java.util.Vector<E>
java.util.Stack<E>
public class Stack<E> extends Vector<E>
修饰符和类型 | 方法 | 描述 |
---|---|---|
E | push(E item) | 将对象压入该栈的顶部 |
synchronized E | pop() | 删除栈顶部的对象,并将该对象作为此函数的值返回 |
synchronized E | peek() | 查看栈顶部的对象,但不将其从栈中移除 |
boolean | empty() | 测试此栈是否为空 |
synchronized int | search(Object o) | 返回对象在栈上的的位置 |
队列 Queue
像栈一样,队列也是表,使用队列时插入在一段进行而删除在另一端进行。队列的基本操作是enqueue(入队),在表的末端(队尾rear)插入元素,dequeue(出队),删除(并返回)在表开头(队头front)的元素。
先进先出(FIFO, first-in-first-out)
Java Queue
Interface Queue<E>
修饰符和类型 | 方法 | 描述 |
---|---|---|
boolean | add(E e) | 如果可以在不违反容量限制的情况下立即将指定的元素插入此队列,成功时返回true,如果当前没有可用空间,则抛出IllegalStateException异常 |
boolean | offer(E e) | 如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中 |
E | remove() | 检索并删除此队列的头 |
E | poll() | 检索并删除此队列的头,如果此队列为空,则返回null |
E | element() | 检索但不删除此队列的头, 如果此队列为空抛出NoSuchElementException异常 |
E | peek() | 检索但不删除此队列的头,如果此队列为空,则返回null |
实现类:
AbstractQueue, ArrayBlockingQueue, ArrayDeque, ConcurrentLinkedDeque, ConcurrentLinkedQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedList, LinkedTransferQueue, PriorityBlockingQueue, PriorityQueue, SynchronousQueue
优先队列 Priority Queue
- 插入操作 O(1)
- 取出操作 O(logn) 按照元素优先级取出
- 底层具体实现的数据结构较为多样和复杂: heap堆、bst(binary search tree)二叉搜索数、treap
Java PriorityQueue
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractQueue<E>
java.util.PriorityQueue<E>
修饰符和类型 | 方法 | 描述 |
---|---|---|
boolean | add(E e) | 将指定的元素插入到此优先级队列中 |
void | clear() | 从这个优先级队列中删除所有元素 |
Comparator<? super E> | comparator() | 返回用于对该队列中的元素排序的比较器,如果该队列按照其元素的自然顺序排序,则返回null |
boolean | contains(Object o) | 是否包含指定元素 |
Iterator |
iterator() | 迭代器 |
boolean | offer(E e) | 将指定的元素插入到此优先级队列中 |
E | peek() | 检索但不删除此队列的头,或如果此队列为空,则返回null |
E | poll() | 检索并删除此队列的头,如果此队列为空,则返回null |
boolean | remove(Object o) | 如果指定元素存在,则从此队列中删除指定元素的单个实例 |
int | size() | 返回元素数目 |
Spliterator |
spliterator() | 在此队列中的元素上创建一个延迟绑定和快速故障Spliterator |
Object[] | toArray() | 返回包含此队列中所有元素的数组 |
toArray(T[] a) | 返回一个包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的类型 |
双端队列 Deque
Deque是双向队列,它支持从两个端点方向检索和插入元素。
Java Deque
Interface Deque<E> extends Queue<E>
修饰符和类型 | 方法 | 描述 |
---|---|---|
void | addFirst(E e) | 在不违反容量限制的情况下立即在deque前面插入指定的元素,如果当前没有可用空间,则抛出IllegalStateException异常 |
void | addLast(E e) | 在不违反容量限制的情况下立即将指定的元素插入此deque的末尾,如果当前没有可用空间,则抛出IllegalStateException异常 |
boolean | offerFirst(E e) | 将指定的元素插入该deque的前面 |
boolean | offerLast(E e) | 将指定的元素插入到该deque的末尾 |
E | removeFirst() | 检索并删除此deque的第一个元素 |
E | removeLast() | 检索并删除此deque的最后一个元素 |
E | pollFirst() | 检索并删除该deque的第一个元素,如果该deque为空,则返回null |
E | pollLast() | 检索并删除该deque的最后一个元素,如果该deque为空,则返回null |
E | getFirst() | 检索,但不删除该deque的第一个元素 |
E | getLast() | 检索,但不删除该deque的最后一个元素 |
E | peekFirst() | 检索但不删除该deque的第一个元素,如果该deque为空,则返回null |
E | peekLast() | 检索但不删除该deque的最后一个元素,如果该deque为空,则返回null |
boolean | removeFirstOccurrence(Object o) | 从此目录中删除指定元素的第一个匹配项 |
boolean | removeLastOccurrence(Object o) | 从此目录中删除指定元素的最后一个匹配项 |
boolean | add(E e) | 在不违反容量限制的情况下立即将指定的元素插入到这个deque所表示的队列中(换句话说,插入到这个deque的尾部),成功时返回true,如果当前没有可用空间,则抛出IllegalStateException异常 |
boolean | offer(E e) | 指定元素插入deque尾部 |
E | remove() | 检索并删除此deque表示的队列的头 |
E | poll() | 检索并删除该deque的第一个元素,如果该deque为空,则返回null |
E | element() | 检索但不删除deque的第一个元素 |
E | peek() | 检索但不删除由该deque表示的队列的头,空返回null |
void | push(E e) | 将元素压入此deque的顶部,如果当前没有可用空间,则抛出IllegalStateException |
E | pop() | 从这个deque表示的栈中弹出一个元素 / 删除并返回该deque的第一个元素 |
boolean | remove(Object o) | 从此目录中删除指定元素的第一个匹配项 |
boolean | contains(Object o) | 是否包含指定元素 |
int | size() | 队列大小 |
Iterator |
iterator() | 迭代器 |
Iterator |
descendingIterator() | 相反顺序元素迭代器 |
实现类:
ArrayDeque, ConcurrentLinkedDeque, LinkedBlockingDeque, LinkedList
LeetCode实战
20. 有效的括号
给定一个只包括 '(',')','{','}','[',']'的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
// 栈 O(n)
public boolean isValid(String s) {
Stack<Character> stack = new Stack<Character>();
for (char c : s.toCharArray()) {
if (c == '(')
stack.push(')');
else if (c == '{')
stack.push('}');
else if (c == '[')
stack.push(']');
else if (stack.isEmpty() || stack.pop() != c)
return false;
}
return stack.isEmpty();
}
155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop()—— 删除栈顶的元素。
top()—— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
pop、top 和 getMin 操作总是在 非空栈 上调用。
class MinStack {
Stack<Integer> stack = new Stack<>();
int min = Integer.MAX_VALUE;
public void push(int x) {
if (x <= min) {
stack.push(min);
min = x;
}
stack.push(x);
}
public void pop() {
int peek = stack.pop();
if (peek == min){
min = stack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return min;
}
}
84. 柱状图中最大的矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
// 1. 暴力求解 O(n^2)
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
int res = 0;
for (int i = 0; i < len; i++) {
// 找左边最后 1 个大于等于 heights[i] 的索引
int left = i;
int curHeight = heights[i];
while (left > 0 && heights[left - 1] >= curHeight) {
left--;
}
// 找右边最后 1 个大于等于 heights[i] 的索引
int right = i;
while (right < len - 1 && heights[right + 1] >= curHeight) {
right++;
}
int width = right - left + 1;
res = Math.max(res, width * curHeight);
}
return res;
}
// 2. 单调栈求解 O(n)
public int largestRectangleArea(int[] heights) {
int len = heights.length;
Stack<Integer> s = new Stack<>();
int maxArea = 0;
for (int i = 0; i <= len; i++){
int h = (i == len ? 0 : heights[i]);
//如果栈是空的,或者当前柱子的高度大于等于栈顶元素所对应柱子的高度,
//直接把当前元素压栈
if (s.isEmpty() || h >= heights[s.peek()]) {
s.push(i);
} else {
int tp = s.pop();
maxArea = Math.max(maxArea, heights[tp] * (s.isEmpty() ? i : i - 1 - s.peek()));
i--;
}
}
return maxArea;
}
239. 滑动窗口最大值
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:
你能在线性时间复杂度内解决此题吗?
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
提示:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
// 1. 暴力求解 O(n*k)
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n * k == 0) return new int[0];
int [] output = new int[n - k + 1];
for (int i = 0; i < n - k + 1; i++) {
int max = Integer.MIN_VALUE;
for(int j = i; j < i + k; j++)
max = Math.max(max, nums[j]);
output[i] = max;
}
return output;
}
// 2. 双端队列求解 O(n)
public int[] maxSlidingWindow(int[] a, int k) {
if (a == null || k <= 0) {
return new int[0];
}
int n = a.length;
int[] r = new int[n-k+1];
int ri = 0;
Deque<Integer> q = new ArrayDeque<>();
for (int i = 0; i < a.length; i++) {
// 移除窗口外元素
if (!q.isEmpty() && q.peek() < i - k + 1) {
q.poll();
}
// 添加一个值前,移除比他小的元素,并且保证窗口中队列头部元素是队列中最大的
while (!q.isEmpty() && a[q.peekLast()] < a[i]) {
q.pollLast();
}
q.offer(i);
if (i >= k - 1) {
r[ri++] = a[q.peek()];
}
}
return r;
}
641. 设计循环双端队列
设计实现双端队列。
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
提示:
所有值的范围为 [1, 1000]
操作次数的范围为 [1, 1000]
请不要使用内置的双端队列库。
42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6