今天跟着左程云算法课015和016,实现【最小栈】和【双端队列】。都是力扣上的mid题,是队列和栈复习的后半部分。
一、最小栈的实现
力扣题目和测试链接:https://leetcode.cn/problems/min-stack/
这个题目要求在常数时间内检索到最小元素的栈,那么我们是不可以遍历的。要实现常数时间内返回最小元素值,可以用两个栈来实现。用两个栈data和min,data用来存放数据,min栈用来同步存放当前最小值。核心思想为:当数据入栈时,判断当前数据val是否大于min栈的栈顶,若大于等于则data.push(val),min.push(ming.peek());若小于则data.push(val),min.push(val)。数据出栈时,data和min栈一起出栈。
可以用Java内部的Stack来实现,也可以用数组实现。
(1)Stack实现最小栈
只需要在push的时候遵循思路即可,其他都非常简单。不过使用Stack的常数时间会比数组要慢。用Stack实现最小栈的代码如下:
//用Java内部的Stack来实现
public class MinStack{
public Stack<Integer> data;
public Stack<Integer> min;
public MinStack() {
data=new Stack<Integer>();
min =new Stack<Integer>();
}
public void push(int val) {
//当min栈为空
if(min.isEmpty()) {
data.push(val);
min.push(val);
}
else {
int num=min.peek();
if(val>num) {
min.push(num);
data.push(val);
}
else {
min.push(val);
data.push(val);
}
}
}
public void pop() {
data.pop();
min.pop();
}
public int top() {
return data.peek();
}
public int getMin() {
return min.peek();
}
}
(2)用数组实现最小栈
核心思想和上面一样,只是需要一个size来做数组栈的栈顶指针,size指向栈顶的后一个位置。核心思路是不变的,代码实现如下:
//用数组实现最小栈
public class MyStack2{
public int MAX=8001;
public int[] data;
public int[] min;
public int size;
public MyStack2() {
data=new int[MAX];
min=new int[MAX];
size=0;
}
public void push(int val) {
//data和min栈都为空
if(size==0) {
data[size]=val;
min[size]=val;
size++;
}
else {
int num=min[size-1];
if(val>num) {
data[size]=val;
min[size]=num;
size++;
}
else {
data[size]=val;
min[size]=val;
size++;
}
}
}
public void pop() {
if(size==0) {
return;
}
else {
size--;
}
}
public int top() {
return data[size-1];
}
public int getMin() {
return min[size-1];
}
}
二、循环双端队列的实现
力扣题目和测试链接:. - 力扣(LeetCode)
设计双端循环队列,也就是说原来的单向队列是前进后出,那么现在双端队列就是两头都可以进出,而队列中的元素要保持一定的次序。实现循环双端队列可以使用双向链表和数组两种存储结构,和上面最小栈的实现相似,使用Java内部的双向链表的常数时间会慢一些,使用数组会快一点。
(1)双向链表(Deque)实现循环双端队列
使用Java内部的双向链表LinkedList,前面在实现队列的时候使用过,这里使用的接口是Deque,可以进行双头操作。具体区别在于:Queue和Deque是Java中两个不同的接口,Deque可以进行双端操作(offerFirst,offerLast,pollFirst,pollLast,peekFirst,peekLast),而Queue不能进行双端操作(只能调用offer,poll和peek)。
//队列接口
public Queue<Integer> queue=new LinkedList<Integer>();
//双端队列接口
public Deque<Integer> deque=new LinkedList<Integer>();
那么使用java内部的双向链表就非常简单了,直接调用Deque接口的函数就可以,代码如下:
public class MyCircularDeque{
public Deque<Integer> deque=new LinkedList<Integer>();
public int size;
public int limit;
public MyCircularDeque(int k) {
size=0;
limit=k;
}
public boolean isEmpty() {
return size==0?true:false;
}
public boolean isFull() {
return size==limit?true:false;
}
public boolean insertFront(int n) {
if(isFull()) {
return false;
}
else {
deque.offerFirst(n);
size++;
return true;
}
}
public boolean insertLast(int n) {
if(isFull()) {
return false;
}
else {
deque.offerLast(n);
size++;
return true;
}
}
public boolean deleteFront() {
if(isEmpty()) {
return false;
}
else {
deque.pollFirst();
size--;
return true;
}
}
public boolean deleteLast() {
if(isEmpty()) {
return false;
}
else {
deque.pollLast();
size--;
return true;
}
}
public int getFront() {
if(isEmpty()) {
return -1;
}
else {
return deque.peekFirst();
}
}
public int getRear() {
if(isEmpty()) {
return -1;
}
else {
return deque.peekLast();
}
}
}
(2)数组实现循环双端队列
其实循环双端队列本质上是循环队列,在上半部分我们实现过循环队列,用size来判空和满,设置两个指针l和r,区别在于循环队列中,加入数据是r端操作,删除数据是l端操作;而双端循环队列的l端和r端都有加入和删除数据,看是从哪一边操作。要注意l和r与0和limit-1的边界关系。
核心思想在于:
头入: l==0:arr[limit-1],l=limit-1; l!=0:arr[l-1],l=l-1;
头出: l==limit-1:l=0; l!=limit-1:l=l+1;
尾进: r==limit-1:arr[0],r=0; r!=limit-1:r=r+1;
尾出: r==0:r=limit-1; r!=0:r=r-1;
那么在具体实现的时候,也是我踩过的坑。在insert数据时要根据size判空,若队列当前为空,则需要将l和r都置为0;这是为什么呢?因为l指向队头数据,r指向队尾数据,他们都指向数据本身。假如队列里有两个数据3和4,l指向3,r指向4,进行一次头出,一次尾出之后,队列为空,但是此时r指向l前面。这一点是需要特别注意的。代码实现如下:
public class MyCircularDeque1{
public int[] arr;
public int size;
public int limit;
public int l;
public int r;
public MyCircularDeque1(int k) {
limit=k;
l=0;
r=0;
size=0;
arr=new int[k];
}
public boolean isEmpty() {
return size==0?true:false;
}
public boolean isFull() {
return size==limit?true:false;
}
public boolean insertFront(int val) {
//若队列为满
if(isFull()) {
return false;
}
//若队列为空
if(isEmpty()) {
l=0;
r=0;
arr[l]=val;
size++;
return true;
}
//若队列不为空
else {
if(l==0) {
arr[limit-1]=val;
l=limit-1;
size++;
return true;
}
else {
arr[l-1]=val;
l=l-1;
size++;
return true;
}
}
}
public boolean insertLast(int val) {
//若队列为满
if(isFull()) {
return false;
}
//若队列为空
if(isEmpty()) {
l=0;
r=0;
arr[r]=val;
size++;
return true;
}
//若队列不为空
else {
if(r==limit-1) {
arr[0]=val;
r=0;
size++;
return true;
}
else{
arr[r+1]=val;
r++;
size++;
return true;
}
}
}
public boolean deleteFront() {
//若队列为空
if(isEmpty()) {
return false;
}
//若队列不为空
else {
if(l==limit-1) {
l=0;
size--;
return true;
}
else {
l++;
size--;
return true;
}
}
}
public boolean deleteLast() {
//若队列为空
if(isEmpty()) {
return false;
}
//若队列不为空
else {
if(r==0) {
r=limit-1;
size--;
return true;
}
else {
r--;
size--;
return true;
}
}
}
public int getFront() {
if(isEmpty()) {
return -1;
}
else {
return arr[l];
}
}
public int getRear() {
if(isEmpty()) {
return -1;
}
else {
return arr[r];
}
}
}