目录
栈
概念
栈是一种特殊的线性结构,只允许在固定的一段进行插入和删除操作。
进行插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的元素遵循先进后出的原则。
操作
入栈(压栈):栈的插入操作叫做入栈。入数据在栈顶
出栈:栈的删除操作。出数据也在栈顶
取栈顶元素:取出栈顶的元素。
实现
- 利用顺序表实现,使用尾插和尾删的方式实现
- 利用链表实现,在头尾都可以实现
public class MyStack {
//利用顺序表实现
private int[] array = new int[100];//不考虑扩容,使用固定大小的数组
private int size=0;
public void push(int v){//入栈
array[size++] = v;
}
public int pop(){//出栈
return array[--size] ;
}
public int peek(){
return array[size-1];//取出栈顶元素
}
public boolean isEmpty(){
return size ==0;
}
public int size(){
return size;
}
}
队列
概念
在一端进行插入数据,在另一端进行数据删除操作的特殊的线性表。
队列具有先进先出(FIFO)的原则。
操作
入队列:在队尾(rear/tail)进行插入元素
出队列:在队头(front/head)进行删除操作
取出队首元素
实现
使用链表的结构实现队列
class Node{
int val;
Node next;
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
public Node(int val) {
this.val = val;
}
}
public class MyQueue {
private Node head = null;
private Node tail = null;
private int size =0;
public void offer(int v){//从队尾进行插入
Node node = new Node(v);
if (tail == null){
head = node;
}else {
tail.next = node;
}
tail = node;
size++;
}
public int poll(){//从队首进行删除
if (size == 0){
throw new RuntimeException("队列为空");
}
Node oldHead = head;
head =head.next;
if (head == null){
tail = null;
}
size--;
return oldHead.val;//返回的是队首元素的值
}
public int peek(){//取出队首元素
if (size == 0){
throw new RuntimeException("队列为空");
}
return head.val;
}
public boolean isEmpty(){
return size ==0;
}
public int size(){
return size;
}
}
循环队列
循环队列通常使用数组实现
循环队列更好的利用了队列的存储空间,当插入到队尾时,如果队首元素已经出队,则从队首接着插入。
队列为空时:front = rear
队列为满时:(rear+1)%maxSize = front
循环队列的长度为:(rear - front + maxSize) % maxSize
双端队列Deque
双端队列是只允许两端都可以进行入队和出队操作的队列。
Deque是“Double ended Queue”的简称。
Java中栈、队列、双端队列的使用
Stack类提供的方法:
方法 | 说明 |
---|---|
E push (E e) | 入栈 |
E pop() | 出栈 |
E peek() | 查看栈顶元素 |
boolean empty() | 判断栈是否为空 |
Queue接口提供的方法:
针对异常的处理有两类方法——抛出异常,返回特殊值
抛出异常 | 返回特殊值 | |
---|---|---|
入队列 | add(e) | offer(e) |
出队列 | remove() | poll() |
查看队首元素 | element() | peek() |
Deque接口继承自Queue,所以Queue接口有的方法,Deque接口也有。
头部元素(队首) | 尾部元素(队尾) | |||
---|---|---|---|---|
抛出异常 | 返回特殊值 | 抛出异常 | 返回特殊值 | |
入队列 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
出队列 | removeFirst() | pollFirst() | removeLast() | pollLast() |
查看队首元素 | getFirst() | peekFirst() | getLast() | peekLast() |
经典面试题
有效括号
https://leetcode-cn.com/problems/valid-parentheses/
思路:根据栈的先进后出的原则,进行括号的有效匹配。
- 准备一个栈,遍历字符串中的每个字符,遇到左括号就进栈
- 遇到右括号就取出栈顶元素进行匹配
- 如果栈为空,说明右括号多,返回false
- 如果不匹配,返回false
- 遍历完数组,如果栈中依然还有元素,说明左括号多余,返回false
public class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new LinkedList<>();
char[] charArray = s.toCharArray();
for(char c : charArray) {
switch(c) {
case '(':
case '[':
case '{':
stack.push(c);
break;
default: {
if(stack.isEmpty()) {
return false;
}
char left = stack.pop();
if(!compareBracket(left ,c)) {
//左右括号不匹配
return false;
}
}
}
}
if(stack.isEmpty()) {
return true;
} else {
//证明是左括号多了
return false;
}
}
//判断括号是否正确匹配
private boolean compareBracket (char left ,char right) {
if(left == '(' && right == ')') {
return true;
}
if(left == '[' && right ==']') {
return true;
}
if(left == '{' && right == '}') {
return true;
}
return false;
}
}
用队列实现栈
https://leetcode-cn.com/problems/implement-stack-using-queues/
思路:
push操作:直接添加到队列中。
pop操作:先将前size-1个元素,出队列,再入队列,最后删除队首元素。
top操作:top操作只是查看栈顶的元素,并不能改变改变原有的元素;先将前size-1个元素,出队列,再入队列,删除队首元素,再将队首元素添加到队列中去。
empty操作:只要判断队列是否为空即可。
pubilc class MyStack {
Queue<Integer> queue = new LinkedList<>();
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.add(x);
}
public int pop() {
int size = queue.size();
for(int i = 0 ;i < size -1 ;i++) {
Integer e = queue.remove();
queue.add(e);
}
return queue.remove();
}
public int top() {
int size = queue.size();
for(int i = 0; i< size -1 ;i++) {
Integer e = queue.remove();
queue.add(e);
}
Integer t =queue.remove();
queue.add(t);
return t;
}
public boolean empty() {
return queue.isEmpty();
}
}
用栈实现队列
https://leetcode-cn.com/problems/implement-queue-using-stacks/
思路:
使用两个栈来实现队列,压入栈s1和输出栈s2;
pop和peek操作,只要输出栈为空,就将压入栈s1中的元素全部压入s2,最后返回s2的栈顶栈顶元素即可。
class MyQueue {
private Deque<Integer> s1;
private Deque<Integer> s2;
public MyQueue() {
s1 = new LinkedList<>();
s2 = new LinkedList<>();
}
public void push(int x) {
s2.push(x);
}
public int pop() {
if (s1.isEmpty()) {
while (!s2.isEmpty()) {
Integer e = s2.pop();
s1.push(e);
}
}
return s1.pop();
}
public int peek() {
if (s1.isEmpty()) {
while (!s2.isEmpty()) {
Integer e = s2.pop();
s1.push(e);
}
}
return s1.peek();
}
public boolean empty() {
return s1.isEmpty() && s2.isEmpty();
}
}
实现最小栈
https://leetcode-cn.com/problems/min-stack/solution/zui-xiao-zhan-by-leetcode-solution/
思路:设计一个辅助栈,与元素同步插入与删除,用于存储每个元素的最小值
元素e要入栈时,取辅助栈的栈顶元素与e比较,得出最小值,及那个最小值插入辅助栈;
元素e要出栈时,辅助栈的元素一并弹出;
栈内元素的最小值就是辅助栈的栈顶元素。
class MinStack {
private Deque<Integer> s1;
private Deque<Integer> s2;
public MinStack() {
s1 = new LinkedList<>();
s2 = new LinkedList<>();
}
public void push(int x) {
s1.push(x);
if(s2.isEmpty()) {
s2.push(x);
} else {
int t = s2.peek();
if(x < t) {
s2.push(x);
} else {
s2.push(t);
}
}
}
public void pop() {
s1.pop();
s2.pop();
}
public int top() {
return s1.peek();
}
public int getMin() {
return s2.peek();
}
}
设计循环队列
https://leetcode-cn.com/problems/design-circular-queue/
思路:
使用数组,通过操作数组的索引来实现循环。
class MyCircularQueue {
private final int[] array;
private int size;
private int frontIndex;
private int rearIndex;
//构造函数
public MyCircularQueue(int k) {
array = new int[k];
size =0;
frontIndex = 0;
rearIndex = 0;
}
//向循环队列中插入元素
public boolean enQueue(int value) {
if(size == array.length) {
return false;
}
array[rearIndex] = value;
size++;
rearIndex++;
//循环
if(rearIndex == array.length) {
rearIndex =0;
}
return true;
}
//删除元素
public boolean deQueue() {
if(size == 0) {
return false;
}
size--;
frontIndex++;
if(frontIndex == array.length) {
frontIndex =0;
}
return true;
}
public int Front() {
if(size == 0) {
return -1;
}
return array[frontIndex];
}
public int Rear() {
if(size == 0) {
return -1;
}
int index = rearIndex -1;
if(index == -1) {
index = array.length -1;
}
return array[index];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == array.length;
}
}