一、有效的括号
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
思路:
使用栈来解题,将之前写的栈拷贝过来使用。创建一个栈,遍历s(s是字符串,可以直接使用s来遍历)。*s如果是左括号那就入栈,是右括号就与栈顶的元素进行判断,类型相同就删除栈顶元素表示这组闭合正确,不同销毁栈返回false。
bool isValid(char* s) {
Stack pc;
StackInit(&pc);
while(*s)
{
if(*s!='(' && *s!='[' && *s!='{')
{
if(*s==')' && StackTop(&pc)!='('||
*s==']' && StackTop(&pc)!='['||
*s=='}' && StackTop(&pc)!='{')
{
StackDestroy(&pc);
return false;
}
StackPop(&pc);
}
else
{
StackPush(&pc,*s);
}
s++;
}
}
这里要注意当*s为右括号,而栈里没有元素时,它将不可能完成闭合。
if(*s!='(' && *s!='[' && *s!='{')
{
if(StackEmpty(&pc))
{
StackDestroy(&pc);
return false;
}
if(*s==')' && StackTop(&pc)!='('||
*s==']' && StackTop(&pc)!='['||
*s=='}' && StackTop(&pc)!='{')
{
StackDestroy(&pc);
return false;
}
StackPop(&pc);
}
遍历结束后要先判断栈是否为空,不为空那有左括号还没有完成闭合,返回false,为空,s内所有括号闭合成功,返回true。
bool isValid(char* s) {
Stack pc;
StackInit(&pc);
while(*s)
{
if(*s!='(' && *s!='[' && *s!='{')
{
if(StackEmpty(&pc))
{
StackDestroy(&pc);
return false;
}
if(*s==')' && StackTop(&pc)!='('||
*s==']' && StackTop(&pc)!='['||
*s=='}' && StackTop(&pc)!='{')
{
StackDestroy(&pc);
return false;
}
StackPop(&pc);
}
else
{
StackPush(&pc,*s);
}
s++;
}
if(!StackEmpty(&pc))
{
StackDestroy(&pc);
return false;
}
StackDestroy(&pc);
return true;
}
二、用队列实现栈
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
定义
使用两个队列实现
typedef struct {
Queue p1;
Queue p2;
} MyStack;
初始化
开辟栈的空间后,主要是对两个队列进行初始化
MyStack* myStackCreate() {
MyStack* tmp=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&tmp->p1);
QueueInit(&tmp->p2);
return tmp;
}
插入
我们的两个队列是要保持至少其中一个为空的(在删除时会说明理由),队列和栈都是对顶进行的插入,所以只需要对有数据的队列进行插入就可以,都为空那插入哪个队列都可以。
void myStackPush(MyStack* obj, int x) {
if(QueueEmpty(&obj->p1))
QueuePush(&obj->p2,x);
else
QueuePush(&obj->p1,x);
}
删除
栈删除是对栈顶的数据进行删除,队列只能删除队头的数据,一个是线性表的头(队列),一个是尾(栈)。一个队列自然无法做到删除,但两个队列就可以做到,在前面提到过两个队列要保持其中一个为空,删除时将有数据的队列(noempty)依次插入到空队列(empty)中,直到noempty剩余一个或0个元素(栈可能为空)时,再将剩余的那个元素删除。这个过程中是将noempty队尾的数据留到了队列中进行删除,empty成了存放数据的队列。注:empty可能是p1或p2,noempty也是。
int myStackPop(MyStack* obj) {
//拿到空队列和有数据的队列
Queue* noempty=&obj->p1;
Queue* empty=&obj->p2;
if(QueueEmpty(&obj->p1))
{
noempty=&obj->p2;
empty=&obj->p1;
}
while(QueueSize(noempty)>1)
{
QueuePush(empty,QueueFront(noempty));
QueuePop(noempty);
}
int tmp = QueueFront(noempty);
QueuePop(noempty);
return tmp;
}
例:栈内的元素是{1,2,3,4,5,6},此时noempty也是相同的值,除队尾的数据外全部插入到empty中,并删除。
最后在将6删除,就实现了栈的删除。
获取栈顶元素
有数据队列的队尾就是栈的栈顶。
int myStackTop(MyStack* obj) {
if(QueueEmpty(&obj->p1))
return QueueBack(&obj->p2);
return QueueBack(&obj->p1);
}
是否为空
两个队列都为空栈才为空。
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->p1)&&QueueEmpty(&obj->p2);
}
销毁
void myStackFree(MyStack* obj) {
QueueInit(&obj->p1);
QueueInit(&obj->p2);
free(obj);
}
三、用栈实现队列
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
定义
使用两个栈实现
typedef struct {
Stack q1;
Stack q2;
} MyQueue;
初始化
MyQueue* myQueueCreate() {
MyQueue* tmp = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&tmp->q1);
StackInit(&tmp->q2);
return tmp;
}
插入
只需要对q1进行插入,q2用来删除。
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->q1,x);
}
删除
如果现在队列内有数据{1,2,3,4,5,6},这些数据都是放在q1内的,此时将q1的数据放到q2中,q2数据的顺序为{6,5,4,3,2,1},删出q2的栈顶1就是原先队列的队头,再依次向后删这组数据就达到了先入先出的效果。后续插入时数据还是先放入q1中,在删除数据时只有当q2为空,再将q1内的数据导入到q2中,否则先入先出的效果会达不到。
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->q2))
{
swap(&obj->q1,&obj->q2);
}
STDataType tmp=StackTop(&obj->q2);
StackPop(&obj->q2);
return tmp;
}
获取队头数据
q2栈顶的元素才是队列的队头元素,在返回q2栈顶元素前要保证q2内有数据。
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->q2))
{
swap(&obj->q1,&obj->q2);
}
return StackTop(&obj->q2);
}
是否为空
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->q1)&&StackEmpty(&obj->q2);
}
销毁
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->q1);
StackDestroy(&obj->q2);
free(obj);
}
四、设计循环队列
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
定义:
s:循环队列是固定的大小,使用连续的空间来储存数据最好。
front/rear:在使用两个变量来指向队头下标和队尾的下一块数据的下标。
k:队列开辟了几个元素。
typedef int QDataType;
typedef struct {
QDataType* s;
int front;
int rear;
int k;
} MyCircularQueue;
初始化:
开辟储存数据的空间时,开辟的大小要比最多存储的元素多一个,这样在判断是否为空和满时不会有冲突。
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* tmp=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
tmp->s=(QDataType*)malloc(sizeof(QDataType)*(k+1));
tmp->front=tmp->rear=0;
tmp->k=k+1;
return tmp;
}
插入:
插入前判断队列是否为满,为满返回false,开辟空间时多开辟了一个,rear也是代表要插入元素的下标,当rear+1与front相等时空间就为满(rear可能是s的最后一个下标,要%k回到头去)。插入后rear要+1更换位置也要%k。
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if((obj->rear+1)%obj->k==obj->front)
{
return false;
}
obj->s[obj->rear]=value;
obj->rear=(obj->rear+1)%obj->k;
return true;
}
删除:
删除判断是否为空,rear和front相等时就为空(如果只开辟一块空间就难以进行判断)。front直接+1就可以,逻辑上只有front到rear间的空间是有效的。
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(obj->rear==obj->front)
return false;
obj->front=(obj->front+1)%obj->k;
return true;
}
获取队头数据:
int myCircularQueueFront(MyCircularQueue* obj) {
if(obj->rear==obj->front)
return -1;
return obj->s[obj->front];
}
获取队尾数据:
rear-1的位置是队尾的下标,但rear-1可能为-1,rear-1+k就是数组的最后一个下标,如果rear不是0这时就会发生越界,需要%k来控制。
int myCircularQueueRear(MyCircularQueue* obj) {
if(obj->rear==obj->front)
return -1;
return obj->s[(obj->rear-1+obj->k)%obj->k];
}
判断是否为空和为满:
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->rear+1)%obj->k==obj->front;
}
销毁:
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->s);
free(obj);
obj=NULL;
}