【LeetCode】【数据结构】栈与队列必刷OJ题

简介: 【LeetCode】【数据结构】栈与队列必刷OJ题

【LeetCode】20.有效的括号(栈的括号匹配问题)

原题链接:🍏有效的括号🍏

题目:给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 1.左括号必须用相同类型的右括号闭合。
  2. 2.左括号必须以正确的顺序闭合。
  3. 3.每个右括号都有一个对应的相同类型的左括号。

根据栈“先入后出”的特性,我们可以利用栈的数据结构进行检验。

当遇到左括号时,入栈,遇到右括号出栈。

最后检查栈中是否还堆积有元素,如果有证明匹配失败,如果栈空,证明匹配成功。

代码实现:

typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;
  int capacity;
}ST;
// 初始化
void STInit(ST* ps)
{
  assert(ps);
  ps->a = NULL;
  ps->capacity = 0;
  ps->top = 0;
}
// 销毁
void STDestroy(ST* ps)
{
  assert(ps);
  free(ps->a);
  ps->a = NULL;
  ps->capacity = ps->top = 0;
}
// 入栈
void STPush(ST* ps, STDataType x)
{
  assert(ps);
  if (ps->capacity == ps->top)
  {
    int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
    STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));
    if (tmp==NULL)
    {
      perror("realloc fail");
      exit(-1);
    }
    ps->a = tmp;
    ps->capacity = newcapacity;
  }
  ps->a[ps->top]=x;
  ps->top++;
}
// 出栈
void STPop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  ps->top--;
}
// 取栈顶元素
STDataType STTop(ST* ps)
{
  assert(ps);
  assert(ps->top > 0);
  return ps->a[ps->top-1];
}
// 判空
bool STEmpty(ST* ps)
{
  assert(ps);
  return ps->top == 0;
}
// 检验是否匹配
bool isValid(char * s)
{
    ST st;
    STInit(&st);
    char val;
    while(*s)
    {
        if(*s=='('||*s=='{'||*s=='[')
        {
            STPush(&st,*s);// 是左括号 入栈
        }
        else
        {
            if(STEmpty(&st))// 排除 首个字符为右括号的情况
            {
                STDestroy(&st);
                return false;
            }
            val=STTop(&st);// 取栈顶字符判断
            STPop(&st);
            if((*s==')'&& val!='(')
            ||(*s==']' && val!='[')
            ||(*s=='}' && val!='{'))// 左右括号不匹配
            {
                STDestroy(&st);
                return false;
            }
        }
        s++;
    }
    bool ret=STEmpty(&st);// 判断数量是否匹配
    STDestroy(&st);
    return ret;
}

【LeetCode】225.用队列实现栈

原题链接:🍏用队列实现栈🍏

题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

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

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

int top() 返回栈顶元素。

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


首先我们知道队列的特性是先入先出,栈的特性是先入后出,题目既然给了我们两个队列,那么一定是利用这两个队列进行捯数据,从而实现栈先入后出的特性。

思路:

每次入栈,利用不空的队列入数据;

当需要出栈时,将非空队列“出队”到空的队列上,直到剩余最后一个元素,取该元素,然后出队,即完成出栈动作。

当需要取栈顶元素时,只需要取非空队列的队尾,此时该队尾即为栈顶元素。

当需要判空时,只需要判断两个队列是否都为空即可。


注意:该题主要考察的其实是大家对于结构的理解,如形参MyStack* obj,实参对应为&obj->q1或&obj->q2,&操作符的优先级低于->,obj是该栈指针,obj->q1为队列结构体,但由于参数为指针类型,所以需要&。

代码实现:

// 队列的基本函数
typedef int QDataType;
typedef struct QueueNode
{
  struct QueueNode* next;
  QDataType data;
}QNode;
typedef struct Queue
{
  QNode* head;
  QNode* tail;
  int size;
}Que;
void QueueInit(Que* pq)
{
  assert(pq);
  pq->head = pq->tail = NULL;
  pq->size = 0;
}
bool QueueEmpty(Que* pq)
{
  assert(pq);
  return pq->head == NULL;
}
void QueueDestroy(Que* pq)
{
  assert(pq);
  QNode* cur = pq->head;
  while (cur)
  {
    QNode* next = cur->next;
    free(cur);
    cur = next;
  }
  pq->head = pq->tail = NULL;
  pq->size = 0;
}
void QueuePush(Que* pq, QDataType x)
{
  assert(pq);
  QNode* newnode = (QNode*)malloc(sizeof(QNode));
  if (newnode == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  if (pq->tail == NULL)
  {
    pq->tail = pq->head = newnode;
  }
  else
  {
    pq->tail->next = newnode;
    pq->tail = newnode;
  }
  pq->size++;
}
void QueuePop(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  if (pq->head->next == NULL)
  {
    free(pq->head);
    pq->head = pq->tail = NULL;
  }
  else
  {
    QNode* next = pq->head->next;
    free(pq->head);
    pq->head = next;
  }
  pq->size--;
}
QDataType QueueFront(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->head->data;
}
QDataType QueueBack(Que* pq)
{
  assert(pq);
  assert(!QueueEmpty(pq));
  return pq->tail->data;
}
int QueueSize(Que* pq)
{
  assert(pq);
  return pq->size;
}
// 以上为队列的基本函数
// 以下为用队列实现栈
// 定义栈
typedef struct
{
  Que q1;
  Que q2;
} MyStack;
// 创建栈
MyStack* myStackCreate()
{
  MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
  QueueInit(&pst->q1);
  QueueInit(&pst->q2);
  return pst;
}
// 入栈
void myStackPush(MyStack* obj, int x)
{
  if (!QueueEmpty(&obj->q1))
  {
    QueuePush(&obj->q1, x);
  }
  else
  {
    QueuePush(&obj->q2, x);
  }
}
// 出栈
int myStackPop(MyStack* obj)
{
    // 假设法,假设q1为空,q2不空
  Que* EmpQue = &obj->q1;
  Que* noEmpQue = &obj->q2;
  if (!QueueEmpty(&obj->q1))
  {
    noEmpQue = &obj->q1;
    EmpQue = &obj->q2;
  }
    // 此时EmpQue一定为空的队列,noEmpQue 一定不为空的队列
    // 将size-1个数据移动到空队列中
  while (QueueSize(noEmpQue) > 1)
  {
    QueuePush(EmpQue, QueueFront(noEmpQue));
    QueuePop(noEmpQue);
  }
    //保存返回值
  int ret = QueueFront(noEmpQue);
  QueuePop(noEmpQue);
  return ret;
}
// 取栈顶元素
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)
{
  QueueDestroy(&obj->q1);
  QueueDestroy(&obj->q2);
    // free栈之前一定要先销毁队列,否则会导致内存泄露
  free(obj);
}

【LeetCode】232.用栈实现队列

原题链接:🍏用栈实现队列🍏

题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

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

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

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

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


与上面用队列实现栈的思路相似,需要两个栈用来捯数据,不同的是,分析过后你会发现这里的两个栈,一个可以固定用来做入队栈(下面统称为pushst),而另外一个固定用来做出队栈(下面统称为popst)。

思路:

入队时,直接push到pushst即可;

出队时,需要进行判断,当popst不为空时,直接出栈popst,注意保存栈顶元素以便返回;当popst为空时,需要将pushst的数据依次捯到popst中,然后出栈popst,同样注意保存栈顶元素以便返回;

返回队头元素时,我们可以写一个返回栈底元素的函数,然后同样进行判断,如果popst为空,我们就返回pushst的栈底;如果popst不为空,我们就返回popst的栈顶即可;

判空时,思路与用队列实现栈相同。

代码实现:

typedef struct 
{
    ST s1;//入队栈pushst
    ST s2;//出队栈popst
} MyQueue;
MyQueue* myQueueCreate() 
{
    MyQueue* pst = (MyQueue*)malloc(sizeof(MyQueue));
    STInit(&pst->s1);
    STInit(&pst->s2);
    return pst;
}
void myQueuePush(MyQueue* obj, int x) 
{
    STPush(&obj->s1,x);
}
int myQueuePop(MyQueue* obj) 
{
    if(!STEmpty(&obj->s2))
    {
        int x=STTop(&obj->s2);
        STPop(&obj->s2);
        return x;
    }
    else
    {
        while(!STEmpty(&obj->s1))
        {
            int x=STTop(&obj->s1);
            STPop(&obj->s1);
            STPush(&obj->s2,x);
        }
        int y=STTop(&obj->s2);
        STPop(&obj->s2);
        return y;
    }
}
int myQueuePeek(MyQueue* obj) 
{
    if(STEmpty(&obj->s2))
    {
        return STbase(&obj->s1);
    }
    else
    {
        return STTop(&obj->s2);
    }
}
bool myQueueEmpty(MyQueue* obj) 
{
    return STEmpty(&obj->s1)&&STEmpty(&obj->s2);
}
void myQueueFree(MyQueue* obj) 
{
    STDestroy(&obj->s1);
    STDestroy(&obj->s2);
    free(obj);
}


【LeetCode】622.设计循环队列

原题链接:🍏设计循环队列🍏

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


循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。


你的实现应该支持如下操作:


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

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

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

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

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

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

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


由于题目中已经明确队列长度,所以定长数组是一个较优的解决方案。

该题目最要首先理解的两个函数为判空和判满。

判空:我们首先肯定会想到当front和rear相等时,即为空。


那么问题来了,如何判满呢?

貌似判空和判满都可以利用front和rear是否相等来判断,如何区分呢?

判满:普遍的解决方案为牺牲一个空间,让该数组始终留有一个空间,用作区分,那么就有以下几种情况,请试着依据下图总结规律,得到判满通用公式。

判满公式:(rear+1)%(k+1)==front

只要了解了这个思想,剩下的就简单很多了。

代码实现:

typedef struct 
{
    int* a;
    int front;
    int rear;
    int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->a=(int*)malloc(sizeof(int)*(k+1));
    obj->front=obj->rear=0;
    obj->k=k;
    return obj;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->front==obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    return (obj->rear+1)%(obj->k+1)==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }
    obj->a[obj->rear]=value;
    obj->rear++;
    obj->rear%=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 -1;
    else
        return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
        return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}
void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}


目录
相关文章
|
5月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
150 1
|
3月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
44 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
8月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
331 77
|
7月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
152 11
|
7月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
12月前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
177 6
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
133 6
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
293 2
|
12月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
188 3
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口