📫作者简介:小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。
📫 热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆 CSDN博客专家 | CSDN后端领域优质创作者 | CSDN内容合伙人 | 2022博客之星
🏆 InfoQ(极客)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
专栏系列(点击解锁)
学习路线(点击解锁)
知识定位
🔥Redis从入门到精通与实战🔥
Redis从入门到精通与实战
围绕原理源码讲解Redis面试知识点与实战
🔥MySQL从入门到精通🔥
MySQL从入门到精通
全面讲解MySQL知识与企业级MySQL实战 🔥计算机底层原理🔥
深入理解计算机系统CSAPP
以深入理解计算机系统为基石,构件计算机体系和计算机思维
Linux内核源码解析
围绕Linux内核讲解计算机底层原理与并发
🔥数据结构与企业题库精讲🔥
数据结构与企业题库精讲
结合工作经验深入浅出,适合各层次,笔试面试算法题精讲
🔥互联网架构分析与实战🔥
企业系统架构分析实践与落地
行业最前沿视角,专注于技术架构升级路线、架构实践
互联网企业防资损实践
互联网金融公司的防资损方法论、代码与实践
🔥Java全栈白宝书🔥
精通Java8与函数式编程
本专栏以实战为基础,逐步深入Java8以及未来的编程模式
深入理解JVM
详细介绍内存区域、字节码、方法底层,类加载和GC等知识
深入理解高并发编程
深入Liunx内核、汇编、C++全方位理解并发编程
Spring源码分析
Spring核心七IOC/AOP等源码分析
MyBatis源码分析
MyBatis核心源码分析
Java核心技术
只讲Java核心技术
本文目录
本文目录
本文导读
一、采用数组和链表实现队列
二、两个栈实现队列
三、设计循环双端队列
四、滑动窗口最大值
总结
本文导读
本文中的例题,采用数组与链表实现队列、用两个栈实现队列、设计循环双端队列、滑动窗口最大值,这几道题完全满足面试中队列的要求。
一、采用数组和链表实现队列
采用数组实现队列:
操作 LinkedList,put元素插入到尾部,pop元素的时候删除头结点
class MyQueue<E> { // 数组实现队列
private LinkedList<E> list = new LinkedList<E>();
private int size = 0;
public void put(E e) {
list.addLast(e); // 插入尾部
size++;
}
public E pop() {
size--;
return list.removeFirst(); // 删除头结点
}
public boolean empty() {
return size == 0;
}
public int size() {
return size;
}
}
采用链表实现队列:
实现一个 node 单链表,put元素的时候插入一个新节点,操作 tail 指针指向新插入的节点,pop元素的时候,直接从头结点取出。
class Node<E> {
Node<E> next = null;
E data;
public Node(E data) { this.data = data; }
}
public class MyQueue<E> {
private Node<E> head = null;
private Node<E> tail = null;
public boolean isEmpty() {
return head == tail;
}
public void put(E data) {
Node<E> newNode = new Node<E>(data);
if (head == null && tail == null) // 队列为空
head = tail = newNode;
else {
tail.next = newNode; // 插入一个新节点
tail = newNode; // tail变成新插入的节点
}
}
public E pop() {
if (this.isEmpty())
return null;
E data = head.data; // 从头结点取出
head = head.next;
return data;
}
public int size() {
Node<E> tmp = head;
int n = 0;
while (tmp != null) {
n++;
tmp = tmp.next;
}
return n;
}
}
二、两个栈实现队列
用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。
假设栈A和栈B用于模拟队列Q,其中A插入栈,B弹出栈,以实现队列Q。假设A和B都是空的,栈A提供了入队的功能,栈B提供了出队的功能。
1、如果栈B不为空,则堆栈B的数据将直接弹出。
2、如果栈B为空,则依次弹出栈A的数据,将其放入栈B,然后弹出栈B的数据。
Stack<Integer> s1 = new Stack<Integer>();
Stack<Integer> s2 = new Stack<Integer>();
public void push(int node) {
s1.push(node);
}
public int pop() {
if (s2.isEmpty()) {
if (s1.isEmpty())
return -1;
while (!s1.isEmpty())
s2.push(s1.pop()); // 则依次弹出栈A的数据,将其放入栈B
}
return s2.pop(); // 弹出栈B
}
三、设计循环双端队列
题目描述:设计实现双端队列,实现 MyCircularDeque 类:
MyCircularDeque(int k) :构造函数,双端队列最大为 k
boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false
boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false
boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false
boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false
int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1
int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1
boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false
boolean isFull() :若双端队列满了,则返回 true ,否则返回 false
解题思路:
构造一个与限定空间 k 等大的数组(capacity = k + 1),使用两下标(front、rear)并配合坐标转换来做,对于下标自增操作而言,只需要进行「加一取模」即可,而对于下标自减操作,由于考虑负值问题,需要进行「增加限定空间偏移后,进行减一再取模」。
对于一个固定大小的数组,只要知道队尾 rear 与队首 front,即可计算出队列当前的长度:( rear − front + capacity) % capacity。
class MyCircularDeque {
private int[] elements; // 保存循环队列的元素
private int front; // front 队列首元素对应的数组的索引
private int rear; // rear 队列尾元素对应的索引的下一个索引
private int capacity; // 循环队列的容量
/**
* 初始化队列
* front,rear 全部初始化为 0
*/
public MyCircularDeque(int k) {
capacity = k + 1;
elements = new int[k + 1];
rear = front = 0;
}
/**
* 在队首插入一个元素
* 将队首 front 移动一个位置,更新队首索引为 front 更新为 (front - 1 + capacity) % capacity
*/
public boolean insertFront(int value) {
if (isFull()) {
return false;
}
front = (front - 1 + capacity) % capacity;
elements[front] = value;
return true;
}
/**
* 在队尾部插入一个元素
* 并同时将队尾的索引 rear 更新为 (rear + 1) % capacity
*/
public boolean insertLast(int value) {
if (isFull()) {
return false;
}
elements[rear] = value;
rear = (rear + 1) % capacity;
return true;
}
/**
* 从队首删除一个元素
* 将队首的索引 front 更新为 (front + 1) % capacity
*/
public boolean deleteFront() {
if (isEmpty()) {
return false;
}
front = (front + 1) % capacity;
return true;
}
/**
* 从队尾删除一个元素
* 将队尾的索引 rear 更新为 (rear - 1 + capacity) % capacity
*/
public boolean deleteLast() {
if (isEmpty()) {
return false;
}
rear = (rear - 1 + capacity) % capacity;
return true;
}
/**
* 返回队首的元素
*/
public int getFront() {
if (isEmpty()) {
return -1;
}
return elements[front];
}
/**
* 返回队尾的元素
*/
public int getRear() {
if (isEmpty()) {
return -1;
}
return elements[(rear - 1 + capacity) % capacity];
}
/**
* 检测队列是否为空
*/
public boolean isEmpty() {
return rear == front;
}
/**
* 检测队列是否已满,判断 front 是否等于 (rear + 1) % capacity == front;
*/
public boolean isFull() {
return (rear + 1) % capacity == front;
}
}
四、滑动窗口最大值
题目描述:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。
你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。
解题思路:
1、遍历给定数组中的元素
2、如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除,直到队列为空或当前考察元素小于新的队尾元素;
3、当队首元素的下标小于滑动窗口左侧边界left时,表示队首元素已经不再滑动窗口内,因此将其从队首移除
4、当窗口右边界right+1大于等于窗口大小k时(数组下标从0开始),意味着窗口形成,此时,队首元素就是该窗口内的最大值。
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length < 2)
return nums;
// 双向队列就是滑动窗口
LinkedList<Integer> queue = new LinkedList();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 如果前面数小,则依次弹出
while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) {
queue.pollLast();
}
// 添加当前值对应的数组下标(比队列头小,才入队)
queue.addLast(i);
// 判断当前队列中队首的值是否有效
if (queue.peek() <= i - k) { // peek()方法返回头对象(不删除)
queue.poll(); // poll()方法返回头对象(会删除)
}
// 在双向队列头的才是有效的数,这个数需要返回到结果数组
if (i + 1 >= k) {
result[i + 1 - k] = nums[queue.peek()];
}
}
return result;
}
总结
本文中的例题,采用数组与链表实现队列、用两个栈实现队列、设计循环双端队列、滑动窗口最大值,这几道题完全满足面试中队列的要求,在实现双向队列时可以使用 LinkedList 。