栈的介绍和实现
栈的介绍
栈(Stack) 是一种后进先出(LIFO, Last In First Out)的数据结构。也就是说,最后一个加入栈的元素是第一个被移除的。栈支持两种主要操作:
push
: 将一个元素压入栈顶。pop
: 从栈顶移除并返回元素。
另外,栈还通常支持以下操作:
peek
或top
: 返回栈顶元素但不移除它isEmpty
: 判断栈是否为空size
: 返回栈中元素的数量
Java 中的栈实现
1. java.util.Stack
- 实现方式: 这是 Java 提供的一个直接的栈类,继承自
Vector
类。它实现了栈的所有基本操作,如push
,pop
,peek
,isEmpty
, 和search
。 - 特点:
- 由于继承自
Vector
,它也是一个动态数组,并且是同步的(线程安全)。 - 每次
push
操作都会在Vector
的末尾添加一个元素,pop
操作则会移除末尾的元素。 Stack
类的用法相对简单,但由于线程安全的特性,性能在多线程环境下会受到一定的影响。
- 由于继承自
import java.util.Stack;
public class StackExample {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 输出 20
}
}
实现一个stack
class MyStack {
private int[] stackArray; // 用于存储栈元素的数组
private int top; // 栈顶指针,指向下一个可用位置
private int capacity; // 栈的容量
// 构造函数,初始化栈的容量
public MyStack(int capacity) {
this.capacity = capacity;
stackArray = new int[capacity];
top = 0; // 初始时栈顶指针为0,表示栈为空
}
// 压栈操作,将元素压入栈中
public void push(int value) {
if (isFull()) {
System.out.println("栈已满,无法再压入元素");
return;
}
stackArray[top++] = value; // 先赋值,再将top加1
}
// 出栈操作,将栈顶元素弹出栈
public int pop() {
if (isEmpty()) {
System.out.println("栈为空,无法弹出元素");
return -1; // 通常返回一个特定值表示栈为空
}
return stackArray[--top]; // 先将top减1,再取值
}
// 查看栈顶元素
public int peek() {
if (isEmpty()) {
System.out.println("栈为空,没有栈顶元素");
return -1;
}
return stackArray[top - 1]; // 取top指针前一位置的值
}
// 判断栈是否为空
public boolean isEmpty() {
return top == 0;
}
// 判断栈是否已满
public boolean isFull() {
return top == capacity;
}
// 返回栈中元素的个数
public int size() {
return top; // top指针指向下一个可用位置,top即为元素个数
}
}
2. java.util.Deque
(双端队列)
- 实现方式:
Deque
接口提供了比Stack
更通用和灵活的栈操作。常见的实现类包括ArrayDeque
和LinkedList
。 - 特点:
Deque
提供了push
,pop
, 和peek
方法,这些方法可以将Deque
用作栈。ArrayDeque
是基于动态数组的实现,性能比Stack
更好,且不需要线程同步。LinkedList
是基于双向链表的实现,适合于需要频繁插入和删除的场景。
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeAsStackExample {
public static void main(String[] args) {
Deque<Integer> stack = new ArrayDeque<>();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 输出 20
}
}
3. java.util.LinkedList
- 实现方式:
LinkedList
类既可以作为栈,也可以作为队列使用,因为它实现了Deque
接口。 - 特点:
LinkedList
的每个元素都是一个节点,包含指向前一个和后一个节点的引用,因此push
和pop
操作的时间复杂度为 O(1)。LinkedList
在频繁的插入和删除操作中表现良好,但相比ArrayDeque
,访问元素的速度较慢。
import java.util.LinkedList;
public class LinkedListAsStackExample {
public static void main(String[] args) {
LinkedList<Integer> stack = new LinkedList<>();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 输出 20
}
}
4. java.util.ArrayDeque
- 实现方式:
ArrayDeque
是基于数组的双端队列,提供栈和队列的双重功能。 - 特点:
- 动态数组结构,不会像
Stack
那样受到线程同步的性能影响。 - 与
LinkedList
不同,ArrayDeque
不会消耗额外的空间来存储链表节点指针。 - 适合于栈操作和队列操作混用的场景。
- 动态数组结构,不会像
import java.util.ArrayDeque;
public class ArrayDequeAsStackExample {
public static void main(String[] args) {
ArrayDeque<Integer> stack = new ArrayDeque<>();
stack.push(10);
stack.push(20);
System.out.println(stack.pop()); // 输出 20
}
}
队列的介绍和实现
队列的介绍
队列(Queue) 是一种先进先出(FIFO, First In First Out)的数据结构。这意味着在队列中,最早加入的元素会最先被移除。队列常用于需要按顺序处理任务或数据的场景,例如在操作系统中管理进程或在网络服务器中处理请求。
队列的基本操作:
- 入队(Enqueue):将一个元素添加到队列的尾部。
- 出队(Dequeue):移除并返回队列的头部元素。
- 查看队头元素(Peek or Front):查看但不移除队列的头部元素。
- 判断队列是否为空(IsEmpty)。
- 判断队列是否已满(IsFull)(在固定大小的队列中)。
队列的实现
可以用数组或链表来实现队列。下面是一个用数组实现的简单队列示例。
双向链表实现
public class Queue {
// 双向链表节点
public static class ListNode {
ListNode next;
ListNode prev;
int value;
// 构造函数,初始化节点
ListNode(int value) {
this.value = value;
}
}
// 队头和队尾指针
private ListNode first; // 队头
private ListNode last; // 队尾
private int size = 0; // 队列大小
// 入队列操作---向双向链表的尾部插入新节点
public void offer(int e) {
ListNode newNode = new ListNode(e);
if (first == null) { // 如果队列为空,新节点既是队头也是队尾
first = newNode;
} else { // 如果队列非空,将新节点插入到队尾
last.next = newNode;
newNode.prev = last;
}
last = newNode; // 更新队尾指针
size++;
}
// 出队列操作---将双向链表的第一个节点删除
public int poll() {
if (first == null) { // 队列为空,返回 -1 表示无元素
return -1;
}
int value = first.value;
if (first == last) { // 队列中只有一个元素,删除后队列变空
last = null;
first = null;
} else { // 队列中有多个元素,删除第一个节点
first = first.next;
first.prev = null;
}
size--; // 减少队列大小
return value;
}
// 获取队头元素---返回链表中第一个节点的值
public int peek() {
if (first == null) { // 队列为空,返回 -1 表示无元素
return -1;
}
return first.value;
}
// 获取队列的大小
public int size() {
return size;
}
// 判断队列是否为空
public boolean isEmpty() {
return first == null;
}
}
java自带
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
System.out.println(queue.poll()); // 输出 1
System.out.println(queue.peek()); // 输出 2
232.用栈实现队列
1.解题思路
用两个栈表示队列,一个是入栈,一个是存放出栈,此时就可以实现队列的进出了
out出栈,一定到出完,再把in的全部pop出,push进out
2.我的实现
实现遇到的问题
代码
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
public MyQueue() {
this.stackIn=new Stack<>();
this.stackOut=new Stack<>();
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
if (!stackOut.isEmpty()){
return stackOut.pop();
}
while (!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
return stackOut.pop();
}
public int peek() {
if (!stackOut.isEmpty()){
return stackOut.peek();
}
while (!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
return stackOut.peek();
}
public boolean empty() {
return stackIn.isEmpty()&&stackOut.isEmpty();
}
}
3.标准实现
class MyQueue {
Stack<Integer> stackIn;
Stack<Integer> stackOut;
/** Initialize your data structure here. */
public MyQueue() {
stackIn = new Stack<>(); // 负责进栈
stackOut = new Stack<>(); // 负责出栈
}
/** Push element x to the back of queue. */
public void push(int x) {
stackIn.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
dumpstackIn();
return stackOut.pop();
}
/** Get the front element. */
public int peek() {
dumpstackIn();
return stackOut.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
// 如果stackOut为空,那么将stackIn中的元素全部放到stackOut中
private void dumpstackIn(){
if (!stackOut.isEmpty()) return;
while (!stackIn.isEmpty()){
stackOut.push(stackIn.pop());
}
}
}
4.题目总结
解题思路里
225. 用队列实现栈
1.解题思路
两个队列实现栈:
每次push进入栈时,当第一个元素push时,都让q1的元素到q2,此时q1为空,然后再add这个元素,然后就可以把q2中的元素add到q1,此时q1就是一个倒序的队列了,可以直接实现栈了。
一个队列实现:
把元素0取出来再加入到队列,把元素1取出来再加入到队列,直到最后一个元素
即把size-1个元素弹出来,取最后一个元素
2.我的实现
实现遇到的问题
代码
两个队列实现栈
class MyStack {//用两个队列实现
Queue<Integer> q1=new ArrayDeque<>();
Queue<Integer> q2=new ArrayDeque<>();
public MyStack() {
}
public void push(int x) {
while (!q1.isEmpty()){
q2.add(q1.poll());
}
q1.add(x);
while (!q2.isEmpty()){
q1.add(q2.poll());
}
}
public int pop() {
return q1.poll();
}
public int top() {
return q1.peek();
}
public boolean empty() {
return q1.isEmpty();
}
}
一个队列实现:
public class MyStack2 {
Queue<Integer> q1=new ArrayDeque<>();
int size=0;
int tmp=0;
public MyStack2() {
}
public void push(int x) {
q1.add(x);
size++;
}
public int pop() {
size--;
tmp=size;
while (tmp>0){
q1.add(q1.poll());
tmp--;
}
return q1.poll();
}
public int top() {
tmp=size-1;
while (tmp>0){
q1.add(q1.poll());
tmp--;
}
int res= q1.peek();
q1.add(q1.poll());
return res;
}
public boolean empty() {
return q1.isEmpty();
}
}
3.标准实现
两个队列
class MyStack {
//q1作为主要的队列,其元素排列顺序和出栈顺序相同
Queue<Integer> q1 = new ArrayDeque<>();
//q2仅作为临时放置
Queue<Integer> q2 = new ArrayDeque<>();
public MyStack() {
}
//在加入元素时先将q1中的元素依次出栈压入q2,然后将新加入的元素压入q1,再将q2中的元素依次出栈压入q1
public void push(int x) {
while (q1.size() > 0) {
q2.add(q1.poll());
}
q1.add(x);
while (q2.size() > 0) {
q1.add(q2.poll());
}
}
public int pop() {
return q1.poll();
}
public int top() {
return q1.peek();
}
public boolean empty() {
return q1.isEmpty();
}
}
一个队列
class MyStack {
Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.add(x);
}
public int pop() {
rePosition();
return queue.poll();
}
public int top() {
rePosition();
int result = queue.poll();
queue.add(result);
return result;
}
public boolean empty() {
return queue.isEmpty();
}
public void rePosition(){
int size = queue.size();
size--;
while(size-->0)
queue.add(queue.poll());
}
}
4.题目总结
解题思路里
20. 有效的括号
1.解题思路
匹配不上就3种情况:
1.左括号多
2.右括号多
3.左右括号不匹配
遇到左括号,就把对应的右括号放到栈里,遇到右括号就与栈顶的右括号匹配。然后此时用右括号如果等于栈的就消除,不等时就不匹配,消完时,右括号还有说明右括号多,栈里还有说明左括号多
2.我的实现
实现遇到的问题
判断stack是否为空
stack == null
检查错误:
stack
不会为null
,因为它已经被初始化为一个空的Stack
对象。- 你应该检查的是
stack.isEmpty()
,而不是stack == null
代码
public boolean isValid(String s) {
Stack<Character> stack=new Stack<>();
if (s.length()%2!=0){
return false;
}
for (char x:
s.toCharArray()) {
//以下3个是用来收集左括号的
if (x=='('){
stack.push(')');
continue;
}
if (x=='['){
stack.push(']');
continue;
}
if (x=='{'){
stack.push('}');
continue;
}
//如果是右括号
//先要判断是不是空栈,空栈操作会报错
//左括号少了
if (stack.isEmpty()){
return false;
}
//左右括号不匹配
if (x!=stack.peek()) {
return false;
}
//匹配左右括号
if (x==stack.peek()){
stack.pop();
}
}
//右括号少了
return stack.isEmpty();
}
3.标准代码
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
//碰到左括号,就把相应的右括号入栈
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {//如果是右括号判断是否和栈顶元素匹配
deque.pop();
}
}
//最后判断栈中元素是否匹配
return deque.isEmpty();
}
}
4.题目总结
解题思路里
1047. 删除字符串中的所有相邻重复项
1.解题思路
遍历字符串,每遍历一个就储存到栈了,判断是否相等
可以·用一个字符串类型去实现栈中的这个功能。
2.我的实现
实现遇到的问题
代码
public String removeDuplicates(String s) {
StringBuilder result =new StringBuilder("");
for (int i = 0; i < s.length(); i++) {
if (result.length()==0||s.charAt(i)!=result.charAt(result.length()-1)){
result.append(s.charAt(i));
}else {
//删除最后一个相等处的元素
result.deleteCharAt(result.length()-1);
}
}
return result.toString();
}
3.标准代码
class Solution {
public String removeDuplicates(String S) {
//ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
//参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
ArrayDeque<Character> deque = new ArrayDeque<>();
char ch;
for (int i = 0; i < S.length(); i++) {
ch = S.charAt(i);
if (deque.isEmpty() || deque.peek() != ch) {
deque.push(ch);
} else {
deque.pop();
}
}
String str = "";
//剩余的元素即为不重复的元素
while (!deque.isEmpty()) {
str = deque.pop() + str;
}
return str;
}
}
4.题目总结
解题思路里