本文选取了一些经典的来自力扣的有关栈与队列的OJ题,致力于帮助读者巩固相关知识概念,并提供优质,值得借鉴的思路供读者积累与掌握。
- 读题:有效括号包括两部分,顺序相同和数量相同。
- 实现:
- 顺序相同:
利用栈后进先出的特点,读取字符串s,遇到左括号就进栈,遇到右括号就出栈查看是否为匹配的左右括号- 数量相同:
遇到右括号时,查看栈是否为空,为空说明右括号多余;
字符串读取完时,查看栈是否为空,不为空说明左括号有剩余
typedef char StackDataType;
typedef struct Stack
{
StackDataType* a;
int top; //栈顶下标
int capacity; //空间容量
}Stack;
//创建并初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
//销毁
void StackDestory(Stack* ps)
{
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//入栈
void StackPush(Stack* ps, StackDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//注意是数组,指针类型为存放数据的类型
StackDataType* tmp = (StackDataType*)realloc(ps->a,sizeof(StackDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x; //在栈顶后插入
ps->top++;
}
//出栈
void StackPop(Stack* ps)
{
assert(ps);
//不为空
assert(ps->top > 0);
ps->top--; //并没有真的“扔出”,只是不再访问这个位置的数据
}
//获取栈顶元素值
StackDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0); //栈顶大于0才拥有有效元素
return ps->a[ps->top - 1];
}
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//检测栈是否为空
bool StackEmpty(Stack* ps) //布尔类型,需包含头文件
{
assert(ps);
return ps->top == 0; //利用表达式返回值,如果表达式为真,则返回ture,否则返回false。
}
bool isValid(char* s) {
//创建栈并初始化
Stack a;
StackInit(&a);
/*while判断顺序是否匹配*/
while (*s)
{
//当访问的s为左括号时进栈
if (*s == '(' || *s == '{' || *s == '[')
StackPush(&a, *s);
//否则保存栈顶值,判断是否为匹配左括号的三个右括号,不为时返回false
else
{
/*如果为空,说明右括号多于,数量不匹配*/
if (StackEmpty(&a))
{
StackDestory(&a);
return false;
}
//栈里面取左括号,此时*s里面应为右括号
char top = StackTop(&a);
StackPop(&a);
//连续&& 和 || 可以一行写一个判断,且将操作符放行头,可以直接对齐
if ((*s == ')' && top != '(')
|| (*s == '}' && top != '{')
|| (*s == ']' && top != '['))
{
StackDestory(&a);
return false;
}
}
++s;
}
/*
if (StackEmpty(&a))
{
StackDestory(&a);
return true;
}
return false;
*/
//利用bool类型精简代码
/*如果不为空,说明左括号多于,数量不匹配*/
bool ret = StackEmpty(&a);
StackDestory(&a);
return ret;
}
重点:满足栈后进先出的性质即可
整体:空队列倒数据,非空队列存数据,初始均为空时随便。
(删除时将不需要删除的数据由非空队列转存进空队列,再删除剩下的即可)
注意点:
1.每次删除(模拟出栈)非空队列和空队列会互换,不是固定的,用假设法判断空与非空非常方便。
2.注意销毁模拟栈时,记得先销毁队列。
typedef int QueueDataType;
typedef struct QueueNode
{
QueueDataType val;
struct QueueNode* next;
}QNode;
typedef struct
{
QNode* phead; //头指针
QNode* ptail; //尾指针
int size; //队内有效元素个数
}Queue;
//创建并初始化(使用的是无头单链表,这里不需要开辟节点,初始化新定义的结构体即可)
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//销毁
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* tmp = cur;
cur = cur->next;
free(tmp);
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//队尾入队(插入)
void QueuePush(Queue* pq,QueueDataType x)
{
assert(pq);
//只有插入需要开辟空间,不需要单独写一个接口
//1.开辟新节点,检验成功后初始化
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
//2.分情况插入 - 为空;不为空
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++; //更新有效元素个数
}
//队头出队(删除)
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->phead); //为空时不能再出栈了
QNode* tmp = pq->phead;
pq->phead = pq->phead->next;
free(tmp);
//仅有一个节点时,需要置空尾指针
if (pq->phead == NULL)
pq->ptail = NULL;
pq->size--; //更新有效元素个数
}
//获取队头元素值
QueueDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead); //为空时没有元素,不能访问队头
return pq->phead->val;
}
//获取队尾元素值
QueueDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail); //为空时没有元素,不能访问队尾
return pq->ptail->val;
}
//获取队列中有效元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
//检查队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL; //利用表达式返回值,表达式为真则返回1,为假则返回0
}
// 以上为队列接口
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
//无论是数组还是链表,我们定义创建接口时都并未开辟空间,只是定义一个外壳,这里的接口函数要求我们一步到位,创建并开辟空间
//开辟整体空间,创建结构体指针
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));
//创建初始化队列
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
void myStackPush(MyStack* obj, int x) {
//一个队列专门存,一个队列专门倒
//存数据的就一直存,所以存进非空队列,初始时随便选一个队列存入作为存队列
if(!QueueEmpty(&obj->q2))
QueuePush(&obj->q2,x);
else
{
QueuePush(&obj->q1,x);
}
}
int myStackPop(MyStack* obj) {
//假设法精简代码,不用动脑子自己判断哪个是存队列,哪个是转队列
Queue* empty = &obj->q2;
Queue* unempty = &obj->q1;
if(!QueueEmpty(&obj->q2))
{
empty = &obj->q1;
unempty = &obj->q2;
}
//将不需要删除的元素由非空转入空
while(unempty->phead != unempty->ptail)
//while(QueueSize(unempty)>1)
{
QueuePush(empty,QueueFront(unempty));
QueuePop(unempty);
}
int top = QueueFront(unempty);
QueuePop(unempty);
return top;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
return QueueBack(&obj->q1);
else
{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {
//两个都为假才为假
return (QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2));
}
void myStackFree(MyStack* obj) {
//注意队列的销毁,free只是把模拟栈内的存放链表指针的结构体释放了,但指针指向的链表是不会被释放的
QueueDestory(&obj->q2);
QueueDestory(&obj->q1);
free(obj);
obj == NULL;
}
整体:利用栈接口,整体思路同队列模拟栈,实现队列先进先出的性质即可。
思路:一个栈专门存数据,一个栈专门删数据,获取数据。两个栈功能恒定不变,实现比模拟队列容易理解。
typedef int StackDataType;
typedef struct Stack
{
StackDataType* a;
int top; //栈顶下标
int capacity; //空间容量
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
//销毁
void StackDestory(Stack* ps)
{
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//入栈
void StackPush(Stack* ps, StackDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//注意是数组,指针类型为存放数据的类型
StackDataType* tmp = (StackDataType*)realloc(ps->a, sizeof(StackDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x; //在栈顶后插入
ps->top++;
}
//出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--; //并没有真的“扔出”,只是不再访问这个位置的数据
}
//获取栈顶元素值
StackDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0); //栈顶大于0才拥有有效元素
return ps->a[ps->top - 1];
}
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//检测栈是否为空
bool StackEmpty(Stack* ps) //布尔类型,需包含头文件
{
assert(ps);
return ps->top == 0; //利用表达式返回值,如果表达式为真,则返回ture,否则返回false。
}
//以上为栈接口
typedef struct {
Stack s1;
Stack s2;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&obj->s1);
StackInit(&obj->s2);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->s1,x);
}
int myQueuePeek(MyQueue* obj) {
//不为空直接获取s2栈顶
if(!StackEmpty(&obj->s2))
{
return StackTop(&obj->s2);
}
//否则先从s1倒数据到s2
while(obj->s1.top>0)
{
StackPush(&obj->s2,StackTop(&obj->s1));
StackPop(&obj->s1);
}
return StackTop(&obj->s2);
}
int myQueuePop(MyQueue* obj) {
int top = myQueuePeek(obj);
StackPop(&obj->s2);
return top;
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->s1) && StackEmpty(&obj->s2);
}
void myQueueFree(MyQueue* obj) {
StackDestory(&obj->s1);
StackDestory(&obj->s2);
free(obj);
}
数组队列实现,不使用接口。
重点:
- 判断为空和为满(为空时back == front;满时back+1 == front,注意数学取模方法)
- 涉及下标操作都要进行数学取模使下标在处于尾部时能循环回头部(循环队列空间恒定不变)
数学取模思想:
适用于循环问题。
x = x % k
x = (x+k) % k
typedef struct {
int* a;
int k;
int front; //队头下标
int back; //队尾下标
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
//1.malloc结构体指针
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//2.malloc数组(大小固定且多开辟一个空间)
obj->a = (int*)malloc(sizeof(int)*(k+1));
obj->k = k;
obj->front = 0;
obj->back = 0;
return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->back == obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->back+1)%(obj->k+1) == obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->back] = value;
//插入完back下标++
obj->back++;
//取模使back绕回头部
obj->back %= (obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return false;
//删除队头,队头下标++
obj->front++;
//先++再取模
obj->front %= (obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
return obj->a[obj->front];
else
return -1;
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(!myCircularQueueIsEmpty(obj))
return obj->a[(obj->back-1 + obj->k+1) % (obj->k+1)];
else
return -1;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
本文精选了栈与队列经典OJ题,如果对你有所帮助,还望点赞收藏支持博主。
文章中有什么不对的丶可改正的丶可优化的地方,欢迎各位来评论区指点交流,博主看到后会一一回复。