一、栈(stack)
1、栈的创建和使用
栈是Vector的一个子类,标准:后进先出
创建:Stack st = new Stack();//Integer 类型的栈
方法:
1 boolean empty()
测试堆栈是否为空。
2 Object peek( )
查看堆栈顶部的对象,但不从堆栈中移除它。
3 Object pop( )
移除堆栈顶部的对象,并作为此函数的值返回该对象。
4 Object push(Object element)
把项压入堆栈顶部。
5 int search(Object element)
返回对象在堆栈中的位置,以 1 为基数。
2、应用
子程序的调用:在跳往子程序前,会将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
处理递归调用:和子程序的调用类似,只是出了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
逆序输出。
表达式的转换[中缀表达式转后缀表达式]与求值
二叉树的遍历。
图的深度优先(depth-first)搜索法。
数制转换:通过求余法,每次将余数进栈,最后将所有余数出栈即可。
括号匹配校验
迷宫求解
实现递归-汉诺塔
3、扩展(单调栈)
单调栈: 顾名思义就是在入栈时遵循单调原则,可以求出一个元素向左(或向右)所能扩展到的最大长度,并不是说在这一段区间内是单调的,而是保证在该区间内该元素一定是最大或最小;
实战例子:
下一个更大元素 II
描述:给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
题解:
class Solution {
public int[] nextGreaterElements(int[] nums) {
int n = nums.length;
int[] ret = new int[n];
Arrays.fill(ret, -1);
Deque<Integer> stack = new LinkedList<Integer>();
for (int i = 0; i < n * 2 - 1; i++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i % n]) {
ret[stack.pop()] = nums[i % n];
}
stack.push(i % n);
}
return ret;
}
}
二、队列
队列是一种特殊的线性表,遵循的原则就是**“先入先出**”
1.队列的基本类型
普通队列(一端进另一端出):
Queue queue = new LinkedList()或Deque deque = new LinkedList()
双端队列(两端都可进出)
Deque deque = new LinkedList()
堆栈
Deque deque = new LinkedList()
注意:Java堆栈Stack类已经过时,Java官方推荐使用Deque替代Stack使用。Deque堆栈操作方法:push()、pop()、peek()。
2.普通队列的操作
压入元素(添加):add()、offer()
相同:未超出容量,从队尾压入元素,返回压入的那个元素。
区别:在超出容量时,add()方法会对抛出异常,offer()返回false
弹出元素(删除):remove()、poll()
相同:容量大于0的时候,删除并返回队头被删除的那个元素。
区别:在容量为0的时候,remove()会抛出异常,poll()返回false
获取队头元素(不删除):element()、peek()
相同:容量大于0的时候,都返回队头元素。但是不删除。
区别:容量为0的时候,element()会抛出异常,peek()返回null。
3、双端队列
Deque接口扩展(继承)了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:
双端队列也可用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:
4、扩展(优先队列)
优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序,可以放基本数据类型的包装类(如:Integer,Long等)或自定义的类对于基本数据类型的包装器类,优先队列中元素默认排列顺序是升序排列。
常用方法:
peek()//返回队首元素
poll()//返回队首元素,队首元素出队列
add()//添加元素
size()//返回队列元素个数
isEmpty()//判断队列是否为空,为空返回true,不空返回false
使用:
1.队列保存的是基本数据类型的包装类
//自定义比较器,降序排列
static Comparator<Integer> cmp = new Comparator<Integer>() {
public int compare(Integer e1, Integer e2) {
return e2 - e1;
}
};
public static void main(String[] args) {
//不用比较器,默认升序排列
Queue<Integer> q = new PriorityQueue<>();
q.add(3);
q.add(2);
q.add(4);
while(!q.isEmpty())
{
System.out.print(q.poll()+" ");
}
/**
* 输出结果
* 2 3 4
*/
//使用自定义比较器,降序排列
Queue<Integer> qq = new PriorityQueue<>(cmp);
qq.add(3);
qq.add(2);
qq.add(4);
while(!qq.isEmpty())
{
System.out.print(qq.poll()+" ");
}
/**
* 输出结果
* 4 3 2
*/
}
2.队列保存的是自定义类
//矩形类
class Node{
public Node(int chang,int kuan)
{
this.chang=chang;
this.kuan=kuan;
}
int chang;
int kuan;
}
public class Test {
//自定义比较类,先比较长,长升序排列,若长相等再比较宽,宽降序
static Comparator<Node> cNode=new Comparator<Node>() {
public int compare(Node o1, Node o2) {
if(o1.chang!=o2.chang)
return o1.chang-o2.chang;
else
return o2.kuan-o1.kuan;
}
};
public static void main(String[] args) {
Queue<Node> q=new PriorityQueue<>(cNode);
Node n1=new Node(1, 2);
Node n2=new Node(2, 5);
Node n3=new Node(2, 3);
Node n4=new Node(1, 2);
q.add(n1);
q.add(n2);
q.add(n3);
Node n;
while(!q.isEmpty())
{
n=q.poll();
System.out.println("长: "+n.chang+" 宽:" +n.kuan);
}
/**
* 输出结果
* 长: 1 宽:2
* 长: 2 宽:5
* 长: 2 宽:3
*/
}
}
3.优先队列遍历
PriorityQueue的iterator()不保证以任何特定顺序遍历队列元素。若想按特定顺序遍历,先将队列转成数组,然后排序遍历
Queue<Integer> q = new PriorityQueue<>(cmp);
int[] nums= {2,5,3,4,1,6};
for(int i:nums)
{
q.add(i);
}
Object[] nn=q.toArray();
Arrays.sort(nn);//数组排序
for(int i=nn.length-1;i>=0;i--)
System.out.print((int)nn[i]+" ");
/**
* 输出结果
* 6 5 4 3 2 1
*/
4.比较器升降序说明
Comparator<Object> cmp = new Comparator<Object>() {
public int compare(Object o1, Object o2) {
//升序
return o1-o2;
//降序
return o2-o1;
}
};
5、补充(Deque)
Deque成为双端队列,是Queue接口的子接口,具体实现类为LinkedList。
Deque可以实现两种数据结构:
若Deque规定从一端进出队列,可以看做栈;若从一端进队,从另一端出队,则可以看做队列。
Deque中的主要方法有:
1.Queue Method:
boolean: add(E e)
作用:将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException异常。
boolean: offer(E e)
作用: 将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于 add(E),后者可能无法插入元素,而只是抛出一个异常。
E : remove()
作用: 获取并移除此队列的头。此方法与 poll 唯一的不同在于:此队列为空时将抛出一个异常。
E: poll()
作用: 获取并移除此队列的头,如果此队列为空,则返回 null。
E: element()
作用: 获取,但是不移除此队列的头。此方法与 peek 唯一的不同在于:此队列为空时将抛出一个异常。
E: peek()
作用: 获取但不移除此队列的头;如果此队列为空,则返回 null。
- Stack Method:
void: push(E e);
作用: 添加元素到栈顶
E :pop();
作用:返回栈顶元素并且删除栈顶元素。
E: peek();
作用:获取但是不删除栈顶元素。
6.单调队列
例子:剑指 Offer 59 - I. 滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 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
算法思路:
窗口对应的数据结构为 双端队列 ,本题使用 单调队列 即可解决以上问题。遍历数组时,每轮保证单调队列 dequedeque :
dequedeque 内 仅包含窗口内的元素 \Rightarrow⇒ 每轮窗口滑动移除了元素 nums[i - 1]nums[i−1] ,需将 dequedeque 内的对应元素一起删除。
dequedeque 内的元素 非严格递减 \Rightarrow⇒ 每轮窗口滑动添加了元素 nums[j + 1]nums[j+1] ,需将 dequedeque 内所有 < nums[j + 1]<nums[j+1] 的元素删除。
算法流程:
(1)初始化: 双端队列 dequedeque ,结果列表 resres ,数组长度 nn ;
(2)滑动窗口: 左边界范围 i \in [1 - k, n - k]i∈[1−k,n−k] ,右边界范围 j \in [0, n - 1]j∈[0,n−1] ;
若 i > 0i>0 且 队首元素 deque[0]deque[0] == 被删除元素 nums[i - 1]nums[i−1] :则队首元素出队;
删除 dequedeque 内所有 < nums[j]<nums[j] 的元素,以保持 dequedeque 递减;
将 nums[j]nums[j] 添加至 dequedeque 尾部;
(3)若已形成窗口(即 i \geq 0i≥0 ):将窗口最大值(即队首元素 deque[0]deque[0] )添加至列表 resres ;
(4)返回值: 返回结果列表 resres ;
代码:
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0) return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
for(int j = 0, i = 1 - k; j < nums.length; i++, j++) {
// 删除 deque 中对应的 nums[i-1]
if(i > 0 && deque.peekFirst() == nums[i - 1])
deque.removeFirst();
// 保持 deque 递减
while(!deque.isEmpty() && deque.peekLast() < nums[j])
deque.removeLast();
deque.addLast(nums[j]);
// 记录窗口最大值
if(i >= 0)
res[i] = deque.peekFirst();
}
return res;
}
}