数据结构和算法(五)——队列及其相关算法
队列基础知识
简介
如图(图片来源极客时间的《数据结构与算法" title="数据结构与算法">数据结构与算法之美》专栏)
只允许队尾入队,队头出队(即先进先出)的存储结构。
顺序队列
使用数组实现的队列,一般面试常考的队列是循环队列(下面介绍实现)。该队列的特点是:
- 队列大小固定
- 出队和入队的时间复杂度为O(1)
链式队列
使用链表的方式实现的队列,该队列的特点为:
- 队列大小不限
- 出队和入队的时间复杂度为O(1)
常见算法
实现一个循环队列
循环队列是由双指针 head 、 tail 分别指向队列头和队列尾来实现的。实现循环队列时要注意其队列是否为空和队列是否已经满的判断。条件如下:
队列为空:head == tail
队列已经满:(tail + 1)%capacity == head
代码如下:
class LoopQueue{int[] queue = null;
int capacity = 0;
int head = 0;
int tail = 0;
public LoopQueue(int capacity) {
this.capacity = capacity;
queue = new int[capacity];
}
public void add(int val){
if((tail + 1) % capacity == head){
System.out.println("队列已满");
}else {
System.out.println("插入值:"+val);
tail = tail%capacity;
queue[tail] = val;
tail++;
}
}
public void remove(){
if(head%capacity == tail){
System.out.println("队列已空");
}else {
head = head%capacity;
int val = queue[head];
System.out.println("删除的值为:"+val);
head++;
}
}
}
设计循环双端队列
设计实现双端队列。你的实现需要支持以下操作:
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
代码如下:
classMyCircularDeque{classNode{
Node next;
int value;
Node(int value){
this.value = value;
}
}
Node head;
Node tail;
int count = 0;
int capcity;
/** Initialize your data structure here. Set the size of the deque to be k. */
publicMyCircularDeque(int k){
head = new Node(-1);
tail = new Node(-1);
capcity = k;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
publicbooleaninsertFront(int value){
if(count >= capcity)returnfalse;
Node next = new Node(value);
if(count == 0){
head.next = next;
tail.next = next;
}else{
Node p = head.next;
head.next = next;
next.next = p;
}
count++;
returntrue;
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
publicbooleaninsertLast(int value){
if(count >= capcity)returnfalse;
Node next = new Node(value);
if(count == 0){
head.next = next;
tail.next = next;
}else{
Node p = tail.next;
tail.next = next;
p.next = next;
}
count++;
returntrue;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
publicbooleandeleteFront(){
if(count == 0)returnfalse;
if(count == 1){
head.next = null;
tail.next = null;
}else{
Node p = head.next;
head.next = p.next;
p.next = null;
}
count--;
returntrue;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
publicbooleandeleteLast(){
if(count == 0)returnfalse;
if(count == 1){
head.next = null;
tail.next = null;
}else{
Node p = tail.next;
Node root = head.next;
while(root!=null&&root.next != p)root = root.next;
tail.next = root;
root.next = null;
}
count--;
returntrue;
}
/** Get the front item from the deque. */
publicintgetFront(){
if(count == 0)return -1;
Node node = head.next;
return node.value;
}
/** Get the last item from the deque. */
publicintgetRear(){
if(count == 0)return -1;
Node node = tail.next;
return node.value;
}
/** Checks whether the circular deque is empty or not. */
publicbooleanisEmpty(){
return count == 0;
}
/** Checks whether the circular deque is full or not. */
publicbooleanisFull(){
return count == capcity;
}
}
滑动窗口的最大值
给定一个数组 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
实现思路:
将窗口看成一个队列,每次滑动就是一次出队和入队操作。
方法一:如果每次都通过比较来获取队列里的最大值,由于队列大小为 k ,则时间复杂度为 O(n * k).
方法二:当滑动窗口出队的值为队列对头元素时,就让队列中的对头元素出队;当滑动窗口入队的值比队尾元素大时,就让队尾元素出队,不停循环直到队尾元素大于入队元素或者队列为空时,再往队列末尾插入元素。这样就可以让队列里的值满足逆序排序,每次只需要取对头元素即为最大值。
代码如下:
classSolution{publicint[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0)returnnewint[0];
if(k == 1)return nums;
int[] res = newint[nums.length - k + 1];
Deque<Integer> l = new LinkedList<>();
for(int i = 0;i < k;i++){
if(l.isEmpty())l.add(nums[i]);
else{
while(!l.isEmpty() && nums[i] > l.peekLast()){
l.pollLast();
}
l.add(nums[i]);
}
}
res[0] = l.peekFirst();
for(int i = k;i < nums.length;i++){
if(nums[i - k] == l.peekFirst())l.pollFirst();
while(!l.isEmpty() && nums[i] > l.peekLast()){
l.pollLast();
}
l.add(nums[i]);
res[i - k + 1] = l.peekFirst();
}
return res;
}
}
必须掌握的代码实现
- 用数组实现一个顺序队列
- 用链表实现一个链式队列
- 实现一个循环队列
面试常考的与队列相关的算法题
- 设计循环双端队列
- 滑动窗口的最大值
以上是 数据结构和算法(五)——队列及其相关算法 的全部内容, 来源链接: utcz.com/a/35750.html