栈与队列OJ问题详解

文章目录

前言

[1. 有效的括号(括号匹配问题)](#1. 有效的括号(括号匹配问题))

[2. 用队列实现栈](#2. 用队列实现栈)

[3. 用栈实现队列](#3. 用栈实现队列)

[4. 设计循环队列](#4. 设计循环队列)

结语


前言

栈(Stack)和队列(Queue)是两种非常重要的线性数据结构,在实际开发中有着广泛的应用。栈遵循后进先出(LIFO)的原则,而队列遵循先进先出(FIFO)的原则。在技术面试和算法竞赛中,栈与队列相关的题目出现频率极高,熟练掌握它们的常见操作和经典问题的解法,是每个程序员必备的技能。

本文精选了4道经典的栈与队列OJ题目,从思路分析到C语言代码实现,逐步详解,每道题目都附带了对应的在线练习链接,方便读者动手实践。希望能帮助读者深入理解栈与队列,轻松应对各类相关题目。

1. 有效的括号(括号匹配问题)

题目描述

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。🔗 力扣 20. 有效的括号

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。

  2. 左括号必须以正确的顺序闭合。

  3. 每个右括号都有一个对应的相同类型的左括号。

思路分析

这道题是栈的经典应用。我们可以利用栈"后进先出"的特性来检验括号的匹配。具体做法:

  • 遍历字符串中的每个字符

  • 如果遇到左括号('(''{''['),就将其压入栈中,因为左括号可以暂时不与右括号匹配

  • 如果遇到右括号(')''}'']'),则检查栈顶元素是否与之匹配:

    • 如果栈为空,说明右括号多余,返回false

    • 如果栈顶元素与当前右括号不匹配,返回false

    • 如果匹配,则将栈顶元素弹出

  • 遍历完整个字符串后,检查栈是否为空:如果栈为空,说明所有括号都匹配成功;否则说明有左括号未被匹配,返回false

代码实现(C语言)

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef char Stackdatatype;
typedef struct stack
{
	Stackdatatype* arr;
	int top;
	int capacity;
}stack;
//栈的初始化以及销毁
void StackInit(stack* pst);
void StackDestroy(stack* pst);
//入栈出栈
void StackPush(stack* pst,Stackdatatype data);
void Stackpop(stack* pst);
Stackdatatype StackTop(stack* pst);//取栈顶元素
//判空
bool Stackempty(stack* pst);
//获取个数
int Stacksize(stack* pst);
void StackInit(stack* pst)
{
	assert(pst!= NULL);
	pst->arr = NULL;
	//这里的top指向的是栈顶元素的下一个位置
	pst->top = pst->capacity = 0;
}
void StackDestroy(stack* pst)
{
	assert(pst!= NULL);
	if (pst->arr)
		free(pst->arr);
	pst->arr = NULL;
	pst->top = pst->capacity = 0;
}
void StackPush(stack* pst, Stackdatatype data)
{
	assert(pst!= NULL);
	//首先判断栈空间是否已经满了
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0)? 4 : 2 * pst->capacity;
		//这里在原来的基础上进行扩容
		Stackdatatype* newarr = (Stackdatatype*)realloc
		(pst->arr,newcapacity * sizeof(Stackdatatype));
		if (newarr == NULL)
		{
			perror("malloc failed");
			exit(1);
		}
		//对相关数据进行更新
		pst->arr = newarr;
		pst->capacity = newcapacity;
	} 
	//栈空间未满,直接存入数据
		
	pst->arr[pst->top++] = data;
}
void Stackpop(stack* pst)
{
	assert(pst!= NULL);
	assert(pst->top != 0);
	pst->top--;
}
Stackdatatype StackTop(stack* pst)
{
	assert(pst!= NULL&&pst->top!= 0);
	return pst->arr[pst->top - 1];
}
bool Stackempty(stack* pst)
{
	assert(pst!= NULL);
	return pst->top == 0;
}
int Stacksize(stack* pst)
{
	assert(pst!= NULL);
	return pst->top;
}
bool isValid(char* s) 
{
    stack pst;
    StackInit(&pst);
    while(*s)
    {
    
        if(*s=='('||*s=='{'||*s=='[')
        {
            StackPush(&pst,*s);
        }
        else
        {
            if(Stackempty(&pst))
            return false;
            if((*s==')'&&StackTop(&pst)!='(')
            ||(*s=='}'&&StackTop(&pst)!='{')
            ||(*s==']'&&StackTop(&pst)!='['))    
           {
             return false;
           }

            Stackpop(&pst);
        }
        s++;
    }
    bool ret=Stackempty(&pst);
    return ret;
}

2. 用队列实现栈

题目描述

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushpoptopempty)。🔗 力扣 225. 用队列实现栈

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶

  • int pop() 移除并返回栈顶元素

  • int top() 返回栈顶元素

  • boolean empty() 如果栈是空的,返回 true;否则,返回 false

思路分析

用队列实现栈的关键在于,队列是先进先出,而栈是后进先出,我们需要在入栈或出栈时调整元素的顺序。常见做法:

  • 使用两个队列,一个主队列 q1 用于存储元素,一个辅助队列 q2 用于临时存储

  • 入栈(push) :将新元素入队到 q1 即可

  • 出栈(pop) :将 q1 中的前 size-1 个元素移到 q2 中,然后 q1 中剩下的最后一个元素就是要出栈的元素,将其出队并返回;最后交换 q1q2 的角色

  • 获取栈顶(top) :类似 pop,但不删除元素,返回 q1 的队尾元素

代码实现(C语言)

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QListdataType;
typedef struct QListNode   //这里是队列的节点结构
{
	struct QListNode* next;
	QListdataType data;
}Qnode;
//这里是队列的结构,这里是为了方便后面的操作,所以使用了结构体
//包含了队列的头指针,尾指针,队列的大小
//否则我们需要多次遍历操作才能实现队列
typedef struct Queue
{
	Qnode* phead;
	Qnode* ptail;
	int size;
}Que;
//队列的初始化以及销毁函数
void QueInit(Que* pq);
void QueDestroy(Que* pq);
//队列的入队操作
void Quepush(Que* pq, QListdataType data);
//队列的出队操作
void Quepop(Que* pq);
//队列的判空操作
bool QueEmpty(Que* pq);
//队列的长度
int QueSize(Que* pq);
QListdataType S(Que* pq);//队头
QListdataType Queback(Que* pq);//队尾
void QueInit(Que* pq)
{
	assert(pq != NULL);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
bool QueEmpty(Que* pq)
{
	return pq->size == 0;
}
void QueDestroy(Que* pq)
{
	assert(pq != NULL);
	Qnode* pcur =pq->phead;
	while (pcur)
	{
		Qnode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->size = 0;
	pq->phead = pq->ptail = NULL;
}
void Quepush(Que* pq, QListdataType data)
{
	assert(pq!=NULL);
	Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		return;
	}
	newnode->data = data;
	newnode->next = NULL;
	if (pq->size==0)//如果一个节点都没有,就需要对头节点也进行更新
		pq->phead = pq->ptail = newnode;
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
void Quepop(Que* pq)
{
	//这里要保证队列中至少要有一个节点
	assert(pq != NULL&&pq->phead!=NULL);
	if (pq->size == 1)//如果只有一个节点
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
		pq->size = 0;
		return;
	}
	Qnode* next = pq->phead->next;
	free(pq->phead);
	pq->phead = next;
	pq->size--;
}
int QueSize(Que* pq)
{
	return pq->size;
}
QListdataType Quefront(Que* pq)
{
	assert(pq != NULL && pq->size != 0);
	return pq->phead->data;
}
QListdataType Queback(Que* pq)
{
	assert(pq != NULL && pq->size != 0);
	return pq->ptail->data;
}
typedef struct 
{
    Que* que1;
    Que* que2;
} MyStack;
MyStack* myStackCreate() 
{
    MyStack* obj=(MyStack*)malloc(sizeof(MyStack));
    obj->que1 = malloc(sizeof(Que));
    obj->que2 = malloc(sizeof(Que));
    QueInit(obj->que1);
    QueInit(obj->que2);
    return obj;
}
void myStackPush(MyStack* obj, int x) 
{
    if(!QueEmpty(obj->que1))//将数据插入不为空的队列
    {
        Quepush(obj->que1,x);
    }
    else
    {
         Quepush(obj->que2,x);
    }
}

int myStackPop(MyStack* obj) 
{
    Que* emptyque=obj->que1;
    Que* unemptyque=obj->que2;
    if(QueEmpty(obj->que2))
    {
        emptyque=obj->que2;
        unemptyque=obj->que1;
    }
    while(unemptyque->size>1)
    {
        Quepush(emptyque,unemptyque->phead->data);
        Quepop(unemptyque);
    }
    int ret=unemptyque->phead->data;
    Quepop(unemptyque);
    return ret;
}
int myStackTop(MyStack* obj) 
{
    return QueEmpty(obj->que1)==true?Queback(obj->que2):Queback(obj->que1);
}
bool myStackEmpty(MyStack* obj) 
{
    return QueEmpty(obj->que1)&&QueEmpty(obj->que2);
}
void myStackFree(MyStack* obj) 
{
    QueDestroy(obj->que1);
    QueDestroy(obj->que2);
    free(obj);
}

3. 用栈实现队列

题目描述

请你仅使用两个栈实现一个先入先出(FIFO)的队列,并支持队列的全部操作(pushpoppeekempty)。🔗 力扣 232. 用栈实现队列

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾

  • int pop() 从队列的开头移除并返回元素

  • int peek() 返回队列开头的元素

  • boolean empty() 如果队列为空,返回 true;否则,返回 false

思路分析

用栈实现队列的思路与上一题类似,但方向相反。我们可以使用两个栈:

  • 入栈(pushStack):专门用于入队操作

  • 出栈(popStack):专门用于出队操作

具体规则:

  • 入队(push) :直接将元素压入 pushStack

  • 出队(pop) :如果 popStack 不为空,则直接从 popStack 弹出栈顶元素;如果 popStack 为空,则将 pushStack 中的所有元素依次弹出并压入 popStack,这样 popStack 中元素的顺序就变成了队列的顺序,然后再从 popStack 弹出栈顶元素

  • 获取队首(peek) :与 pop 类似,但不删除元素

代码实现(C语言)

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int Stackdatatype;
typedef struct stack
{
	Stackdatatype* arr;
	int top;
	int capacity;
}stack;
//栈的初始化以及销毁
void StackInit(stack* pst);
void StackDestroy(stack* pst);
//入栈出栈
void StackPush(stack* pst,Stackdatatype data);
void Stackpop(stack* pst);
Stackdatatype StackTop(stack* pst);//取栈顶元素
//判空
bool Stackempty(stack* pst);
//获取个数
int Stacksize(stack* pst);
void StackInit(stack* pst)
{
	assert(pst!= NULL);
	pst->arr = NULL;
	//这里的top指向的是栈顶元素的下一个位置
	pst->top = pst->capacity = 0;
}
void StackDestroy(stack* pst)
{
	assert(pst!= NULL);
	if (pst->arr)
		free(pst->arr);
	pst->arr = NULL;
	pst->top = pst->capacity = 0;
}
void StackPush(stack* pst, Stackdatatype data)
{
	assert(pst!= NULL);
	//首先判断栈空间是否已经满了
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0)? 4 : 2 * pst->capacity;
		//这里在原来的基础上进行扩容
		Stackdatatype* newarr = (Stackdatatype*)realloc
		(pst->arr,newcapacity * sizeof(Stackdatatype));
		if (newarr == NULL)
		{
			perror("malloc failed");
			exit(1);
		}
		//对相关数据进行更新
		pst->arr = newarr;
		pst->capacity = newcapacity;
	} 
	//栈空间未满,直接存入数据
		
	pst->arr[pst->top++] = data;
}
void Stackpop(stack* pst)
{
	assert(pst!= NULL);
	assert(pst->top != 0);
	pst->top--;
}
Stackdatatype StackTop(stack* pst)
{
	assert(pst!= NULL&&pst->top!= 0);
	return pst->arr[pst->top - 1];
}
bool Stackempty(stack* pst)
{
	assert(pst!= NULL);
	return pst->top == 0;
}
int Stacksize(stack* pst)
{
	assert(pst!= NULL);
	return pst->top;
}
typedef struct 
{
    stack* pushstack;//专门用来放入队元素的栈
    stack* popstack;//专门用来放出队元素的栈
} MyQueue;
MyQueue* myQueueCreate() 
{
    MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
    obj->pushstack=(stack*)malloc(sizeof(stack));
    obj->popstack=(stack*)malloc(sizeof(stack));
    StackInit(obj->pushstack);
    StackInit(obj->popstack);
    return obj;
}

void myQueuePush(MyQueue* obj, int x) 
{
    StackPush(obj->pushstack,x);
}

int myQueuePop(MyQueue* obj) 
{
    if (Stackempty(obj->popstack))
    {
    while(!Stackempty(obj->pushstack))
    {
        StackPush(obj->popstack,StackTop(obj->pushstack));
        Stackpop(obj->pushstack);
    }
    }
    int ret=StackTop(obj->popstack);
    Stackpop(obj->popstack);
    return ret;
}
int myQueuePeek(MyQueue* obj) 
{
   if (Stackempty(obj->popstack))
    {
    while(!Stackempty(obj->pushstack))
    {
        StackPush(obj->popstack,StackTop(obj->pushstack));
        Stackpop(obj->pushstack);
    }
    }
    int ret=StackTop(obj->popstack);
    return ret;
}

bool myQueueEmpty(MyQueue* obj) 
{
    if(Stackempty(obj->popstack)&&Stackempty(obj->pushstack))
    return true;
    else
    return false;
}

void myQueueFree(MyQueue* obj) 
{
    StackDestroy(obj->popstack);
    StackDestroy(obj->pushstack);
    free(obj);
}

4. 设计循环队列

题目描述

设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。🔗 力扣 622. 设计循环队列

实现 MyCircularQueue 类:

  • MyCircularQueue(k):构造器,设置队列长度为 k

  • enQueue(value):向循环队列插入一个元素。如果成功插入则返回真

  • deQueue():从循环队列中删除一个元素。如果成功删除则返回真

  • Front():从队首获取元素。如果队列为空,返回 -1

  • Rear():获取队尾元素。如果队列为空,返回 -1

  • isEmpty():检查循环队列是否为空

  • isFull():检查循环队列是否已满

思路分析

循环队列通常使用数组实现,通过取模运算让数组在逻辑上形成一个环。实现时需要区分队空和队满状态。常见的做法是留一个空位 ,即当 (rear + 1) % capacity == front 时认为队满,这样 front == rear 时就表示队空。

文档中给出了循环队列的示意图,展示了空的循环队列和队满时的状态(rear+1=front)[citation:课件Page6]。

代码实现(C语言)

cpp 复制代码
typedef struct 
{
    int* arr;
    int head;
    int tail;
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* obj=( MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr=(int*)malloc(sizeof(int)*(k+1));
    obj->head=obj->tail=0;
    obj->k=k;
    return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) //插入数据
{
    if(myCircularQueueIsFull(obj))//如果满了
    return false;
    else
   { 
    obj->arr[obj->tail]=value;
    obj->tail++;
    obj->tail%=(obj->k+1);
    return true;
   }
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) //删除数据
{
    if(obj->head==obj->tail)//如果循环链表为空
    return false;
    obj->head=(obj->head+1)%(obj->k+1);//这里对head也需要取模
    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(obj->head==obj->tail)//如果循环链表为空
    return -1;
    else
    return obj->arr[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
     if(obj->head==obj->tail)//如果循环链表为空
    return -1;
    else
    return obj->arr[(obj->tail-1+obj->k+1)%(obj->k+1)];
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->head==obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->tail+1)%(obj->k+1)==obj->head;
}

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->arr);
    free(obj);
}

结语

栈和队列作为基础数据结构,其核心在于理解它们的特性灵活运用这两种结构解决问题。通过以上4道经典题目的练习,相信大家已经掌握了栈和队列操作的基本技巧:

  • 的"后进先出"特性在括号匹配、表达式求值等问题中大显身手

  • 队列的"先进先出"特性则适用于排队场景

  • 用栈实现队列用队列实现栈这两道题,考察的是对两种数据结构特性的深入理解

  • 循环队列则是在有限空间内高效利用资源的经典设计

下一篇文章我们将开始二叉树的学习。

相关推荐
fengxin_rou1 小时前
一文读懂 Redis 集群:从哈希槽到透明访问
java·数据库·redis·算法·spring·缓存
DeepModel1 小时前
【概率分布】t分布详解
算法·概率论
CoovallyAIHub2 小时前
ICLR 2026 | 慕尼黑工大院士Navab团队联合MVTec提出FoundAD,用基础视觉编码器实现少样本异常检测
人工智能·算法·计算机视觉
仰泳的熊猫2 小时前
题目2266:蓝桥杯2015年第六届真题-打印大X
数据结构·c++·算法·蓝桥杯
wefg12 小时前
【算法】约数个数、约数和
算法
波哥学开发2 小时前
自动驾驶必备:全面解析鱼眼相机投影模型(UCM/KB/DS)及实战代码
算法
云泽8082 小时前
蓝桥杯算法精讲:二分算法之二分答案深度剖析
算法·蓝桥杯
小龙报2 小时前
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现
c语言·数据结构·c++·物联网·算法·链表·visualstudio
佩奇大王2 小时前
P2118 排列字母
java·开发语言·算法