栈的声明+初始化:Stack s=new Stack<>();
重点:
上面两个方法是ArrayDeque类中的两个方法
方法1:将ArrayDeque类型转化为Object[]类型
方法2:将ArrayDeque类型转化为T[]类型
注意事项:
1、不能将Object[] 转化为String[],转化的话只能是取出每一个元素再转化。java中的强制类型转换只是针对单个对象的,想要偷懒将整个数组转换成另外一种类型的数组是不行的,这和数组初始化时需要一个个来也是类似的。
2、Character[]不能直接强转成char[],需要一个个转化。
总之,想使用ArrayDeque类中的toArray方法,转换成基本类型数组的话,还是比较麻烦的,必须使用循环一个个转化
**三、滑动窗口最大值 -单调队列 (递增或递减) **
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 1:
输入: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
//自定义数组
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
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();
//先将前k的元素放入队列
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;
}
}
其实,大家可以自己观察一下单调队列的实现,nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,所以整体的复杂度还是 O(n)。
空间复杂度因为我们定义一个辅助队列,所以是O(k)。
PriorityQueue(Comparator super E> comparator)
创建具有默认初始容量的 PriorityQueue ,并根据指定的比较器对其元素进行排序。
大顶堆
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
根据map的value值正序排,相当于一个小顶堆
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
1、
(o1, o2) -> o1.getValue() - o2.getValue()
相当于
new Comparator
public int compare(Map.Entry
return pair1.getValue() - pair2.getValue() ;
}
}
2、(o1, o2) -> o1.getValue() - o2.getValue()小顶堆 队列头放的是最小元素
(o1, o2) -> o2.getValue() - o1.getValue()大顶堆 队列头放的是最大元素
3、大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反而建立大顶堆,比较困惑。确实 例如我们在写快排的cmp函数的时候,return left>right 就是从大到小,return left
PriorityQueue类:
方法 | 说明 |
---|---|
boolean add(E e) | 将指定的元素插入到此优先级队列中。 |
void clear() | 从此优先级队列中删除所有元素。 |
Comparator super E> comparator() | 返回用于为了在这个队列中的元素,或比较null如果此队列根据所述排序natural ordering的元素。 |
boolean contains(Object o) | 如果此队列包含指定的元素,则返回 true 。 |
Iterator iterator() | 返回此队列中的元素的迭代器。 |
boolean offer(E e) | 将指定的元素插入到此优先级队列中。 |
E peek() | 检索但不删除此队列的头,如果此队列为空,则返回 null 。 |
E poll() | 检索并删除此队列的头,如果此队列为空,则返回 null 。 |
boolean remove(Object o) | 从该队列中删除指定元素的单个实例(如果存在)。 |
int size() | 返回此集合中的元素数。 |
Spliterator spliterator() | 在此队列中的元素上创建late-binding和失败快速 Spliterator 。 |
Object[] toArray() | 返回一个包含此队列中所有元素的数组。 |
T[] toArray(T[] a) | 返回一个包含此队列中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 |
1、 Stack
问题一、C++中stack 是容器么?
Java中Stack类继承了Vector类,在其基础上实现了了栈的功能。由于是直接继承而非通过接口进行隐藏(如Queue虽然由LinkedList实现,但对其非队列接口进行了隐藏),Java的Stack拥有Vector的所有方法并且继承了其线程安全特性(所以也和Vector一样在性能上有所损耗)。
在List的基础上,Stack添加了以下方法:
push:向栈中压入一个元素并返回该元素。
peek:获取栈顶元素,栈为空抛出异常。
pop:获取并弹出栈顶元素,栈为空抛出异常。
empty:同isEmpty()。
search:基于lastIndexOf()实现,返回搜索元素离栈顶的最近距离。
可见,Stack是一个古老的,并为了模拟栈的操作不惜重复实现同一函数的方法。当比较注重效率时,显然基于LinkedList或者ArrayList对栈重新实现,会有更好的效果。
2、Queue
从继承层次上看,Queue和List,Map,Set一样,是直接继承了Collections类的容器类,但是从其实现上看,Queue又可以视为一种特殊的List。原因是Queue的直接实现方法是LinkedList,并对其功能加以限制,仅暴露了Queue支持的功能:add(),remove(),element(),offer(),poll(),peek(),put(),take()。当然LinkedList这些功能对于List接口,也并非全部暴露。
Queue对象能够使用以下方法操作:
add:队列末尾添加一个元素,若队列已满抛出异常。
offer:队列末尾添加一个元素,若队列已满返回false,成功返回true。另外,可以附加时间,时间单位参数设置超时。
remove:移除并返回队列头部元素,若队列已空抛出异常。
poll:移除并返回队列头部元素,若队列已空返回Null。另外,可以附加时间,时间单位参数设置超时。
element:返回队列头部元素,若队列已空抛出异常。
peek:返回队列头部元素,若队列已空返回Null。
需要注意的是,由于队列中poll和peek操作以Null为标志,所以队列中添加Null元素是不合法的。
3、 PriorityQueue
在介绍完Stack和Queue后,自然要说另一个最基本的数据结构——Heap,也就是这里的PriorityQueue优先队列。有关优先队列,我在很久很久以前介绍过C++的优先队列,Java也一样,重点部分是对Comparator的操作。
由于知道PriorityQueue是基于Heap的,当新的元素存储时,会调用siftUpUsingComparator方法,其定义如下:
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
我们从代码可以看出,当Comparator的返回值为负时,会进行siftUp操作。例如,如果我们想让数值大的数字(下例子中为a)先出列,则让compare(a,b),则返回-1即可。可以看出来,和C++类似,方法取得的是“不小于”的概念。我们定义的方法可以视为是一个“小于”方法。
另外我们要注意到Comparator这个接口尽管有很多方法,但是有@FunctionalInterface标志,说明这是一个函数接口,换言之,在Java8中,我们可以使用lambda表达式来写这个方法。
用匿名类方法写的Comparator:
PriorityQueue priorityQueue=new PriorityQueue(new Comparator<Student>() {
@Override
public int compare(Student student1, Student student2) {
return student1.mark.compareTo(student2.mark);
}
});
用lambda表达式写Comparator
PriorityQueue priorityQueue=new PriorityQueue((Comparator<Student>)(student1, student2)->student1.mark.compareTo(student2.mark));
这里需要注意的是,如果不加上类型转换,Java无法正确推断lambda表达式的类型。
问题二、stack 提供迭代器来遍历stack空间么?
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。不像是set 或者map 提供迭代器iterator来遍历所有元素。