栈的面试题:
1.有效的括号
题目:
有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
思路:
- 首先我们可以知道我们需要去比较字符串内的元素,并且我们需要用到后进先出的场景,因此这里我们考虑用栈来解决问题
- 我们将前括号放到栈内,s指针如果指向的是后括号,就让其和栈内的栈顶元素对比,如果匹配就将栈顶元素弹出,s继续遍历
- 一旦不匹配,或者栈空了,s还有后括号没有匹配,或者栈还有元素,s没有后括号匹配了就是无效字符串,返回false
代码实现:
由于我们是使用C语言写oj题,因此我们需要自己去编写栈的定义和栈的接口实现
如果是在leetcode上,头文件之类的自己会包含,我们不用去管
接口:
// 这里的栈我们用动态顺序表实现 (也可以用静态顺序表实现[不好扩容和定义空间大小])
# include<stdio.h>
# include<assert.h>
# include<stdlib.h>
# include<stdbool.h>
typedef char SLDataType;
typedef struct Stack
{
SLDataType* _a;
int _top; // 栈顶下标 [规定栈顶下标:最后一个有效数据的下一个位置]
int _capacity; // 数组的有效空间大小
}Stack;
// 栈的初始化
void StackInit(Stack* ps);
// 栈的销毁
void StackDestory(Stack* ps);
// 栈是能从栈顶 存数据或者取数据,因此不存在尾插头插之类的
// 入栈
void StackPush(Stack* ps, SLDataType x);
// 出栈
void StackPop(Stack* ps);
// 栈的数据个数获取
//int StackSize(Stack st); //其实理论上获取元素个数只需要传值调用就行 但是为了保持接口一致性,我们采用指针
int StackSize(Stack* ps);
// 获取栈顶元素
SLDataType StackTop(Stack* ps);
// 判断栈是否为空
int StackEmpty(Stack* ps); // 是空返回1 不是空的返回0
// 栈的初始化
void StackInit(Stack* ps)
{
assert(ps); // ps不能为NULL
// 栈的初始化
/*ps->_a = NULL;
ps->_top = 0;
ps->_capacity = 0;*/
// 除了上面这种初始化。也可以这样初始化
SLDataType* tmp = (SLDataType*)malloc(sizeof(SLDataType) * 4); // 这样后面入栈时无需判断 空间是否为0
if (tmp == NULL)
{
perror("StackInit():malloc()");
return;
}
ps->_a = tmp;
ps->_top = 0;
ps->_capacity = 4;
}
// 栈的销毁
void StackDestory(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_top = ps->_capacity = 0;
}
// 入栈
void StackPush(Stack* ps, SLDataType x)
{
assert(ps);
// 插入之前 判断栈的空间是否足够新的数据插入
if (ps->_top == ps->_capacity) // 判断空间是否足够
{
int newcapacity = ps->_capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->_a, sizeof(SLDataType) * newcapacity); // 增容
if (tmp == NULL) // 判断是否增容成功
{
perror("StackPush():realloc()");
return;
}
// 更新栈
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_a[ps->_top] = x; // 入栈
ps->_top++; // 让top记录的是栈顶 也就是最后一个数据的下一个位置
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0); // 栈里面要有数据才能出栈
ps->_top--; // 让top--就行 最后一个数据的下标是 top - 1
}
// 栈的数据个数获取
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top; // top代表栈顶下标,是最后的一个数据的下标 + 1 其实就是栈的数据个数
}
// 获取栈顶元素
SLDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0); // 没有数据还怎么获取
return ps->_a[ps->_top - 1]; // top是栈顶下标,top - 1才是最后一个数据的下标
}
// 判断栈是否为空
int StackEmpty(Stack* ps) // 是空返回1 不是空的返回0
{
assert(ps);
return ps->_top == 0 ? 1 : 0; // ps->pos只要为0就说明栈内没有数据了
//return !ps->_top; // ps->top 为0 就返回1,为真就返回 0 ,除了0的数都是真
}
代码:
bool isValid(char* s)
{
// 由于这道题需要用到后进先出的特性,因此我们使用栈来解决
// 创建一个栈
Stack st;
StackInit(&st); // 初始化
bool ret = true; // 用来判断字符串是否有效
// 遍历字符串
while (*s != '\0')
{
// 如果s指针指向的是前括号就入栈
if (*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
s++; // 让s往后走
}
else
{
// 走到这里有可能是s后括号多,栈内已经没有前括号了,那后面去取栈顶元素自然无法取出
if (StackEmpty(&st))// 判断栈是否空了
{
// 走进来就说明栈内没有元素了,但是s还有后括号
ret = false; // 无效字符串
break;
}
// 判断s下一步指向的是否是后括号,是否匹配栈顶的前括号
char top = StackTop(&st); // 取出栈顶元素
// 每一种括号都要判断一下是否匹配到
if (*s == ')' && top != '(')
{
// 走到这里说明没有匹配上
ret = false; // 无效字符串
break; // 不在这里return false是因为会有内存泄漏问题,跳出循环去外面统一调用销毁函数
}
if (*s == ']' && top != '[')
{
// 走到这里说明没有匹配上
ret = false; // 无效字符串
break;
}
if (*s == '}' && top != '{')
{
// 走到这里说明没有匹配上
ret = false; // 无效字符串
break;
}
// 走到这里说明有括号配对成功,让s继续往后遍历
s++;
// 栈顶元素匹配成功之后要弹出来,防止后面还有括号要配对
StackPop(&st);
}
}
// 走到这里,有可能是全部匹配完是true。
//也有可能是s字符串只有前括号比后括号多 退出循环时,栈内还有许多前括号
if (!StackEmpty(&st)) // 判断栈是否为空
ret = false; // 不是空的就是无效字符串
StackDestory(&st); // 销毁栈
if (ret == false)
return false;
// 走到这里就说明是有效字符串
return true;
}
2.用栈实现队列
题目:
用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
思路:
-
要通过两个栈实现先进先出的队列,我们要思考数据转移的特性
-
我们发现,我们把栈的数据转移到另外一个栈的时候,数据的顺序会倒转
-
然后我们发现,这样就是先进先出了,1,2, 3, 4压进去,出来也是从栈顶出来,1, 2, 3, 4。 也就是说 第一个栈的栈顶就是队列的队尾,第二个栈的栈顶就是队列的队头。
-
- 那我们给这个队列插入数据时候,要从队尾插入,也就是要把数据从第二个栈全部转移到第一个栈。
- 队列导出数据的时候,也就是从队头出,那就要把数据从第一个栈全部转移到第二个栈。
代码实现:
接口:
// 这里的栈我们用动态顺序表实现 (也可以用静态顺序表实现[不好扩容和定义空间大小])
typedef int SLDataType;
typedef struct Stack
{
SLDataType* _a;
int _top; // 栈顶下标 [规定栈顶下标:最后一个有效数据的下一个位置]
int _capacity; // 数组的有效空间大小
}Stack;
// 栈的初始化
void StackInit(Stack* ps);
// 栈的销毁
void StackDestory(Stack* ps);
// 栈是能从栈顶 存数据或者取数据,因此不存在尾插头插之类的
// 入栈
void StackPush(Stack* ps, SLDataType x);
// 出栈
void StackPop(Stack* ps);
// 栈的数据个数获取
//int StackSize(Stack st); //其实理论上获取元素个数只需要传值调用就行 但是为了保持接口一致性,我们采用指针
int StackSize(Stack* ps);
// 获取栈顶元素
SLDataType StackTop(Stack* ps);
// 判断栈是否为空
int StackEmpty(Stack* ps); // 是空返回1 不是空的返回0
// 栈的初始化
void StackInit(Stack* ps)
{
assert(ps); // ps不能为NULL
// 栈的初始化
/*ps->_a = NULL;
ps->_top = 0;
ps->_capacity = 0;*/
// 除了上面这种初始化。也可以这样初始化
SLDataType* tmp = (SLDataType*)malloc(sizeof(SLDataType) * 4); // 这样后面入栈时无需判断 空间是否为0
if (tmp == NULL)
{
perror("StackInit():malloc()");
return;
}
ps->_a = tmp;
ps->_top = 0;
ps->_capacity = 4;
}
// 栈的销毁
void StackDestory(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_top = ps->_capacity = 0;
}
// 入栈
void StackPush(Stack* ps, SLDataType x)
{
assert(ps);
// 插入之前 判断栈的空间是否足够新的数据插入
if (ps->_top == ps->_capacity) // 判断空间是否足够
{
int newcapacity = ps->_capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->_a, sizeof(SLDataType) * newcapacity); // 增容
if (tmp == NULL) // 判断是否增容成功
{
perror("StackPush():realloc()");
return;
}
// 更新栈
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_a[ps->_top] = x; // 入栈
ps->_top++; // 让top记录的是栈顶 也就是最后一个数据的下一个位置
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0); // 栈里面要有数据才能出栈
ps->_top--; // 让top--就行 最后一个数据的下标是 top - 1
}
// 栈的数据个数获取
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top; // top代表栈顶下标,是最后的一个数据的下标 + 1 其实就是栈的数据个数
}
// 获取栈顶元素
SLDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0); // 没有数据还怎么获取
return ps->_a[ps->_top - 1]; // top是栈顶下标,top - 1才是最后一个数据的下标
}
// 判断栈是否为空
int StackEmpty(Stack* ps) // 是空返回1 不是空的返回0
{
assert(ps);
return ps->_top == 0 ? 1 : 0; // ps->pos只要为0就说明栈内没有数据了
//return !ps->_top; // ps->top 为0 就返回1,为真就返回 0 ,除了0的数都是真
}
代码(自己实现的版本):
typedef struct
{
Stack _s1;
Stack _s2;
} MyQueue;
MyQueue* myQueueCreate()
{
// 创建栈
MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&pq->_s1);
StackInit(&pq->_s2);
return pq;
}
void myQueuePush(MyQueue* obj, int x)
{
// 给队列插入元素,要在第一个栈插入
// 如果第二个栈有数据,要将其全部转移到第一个栈
if(!StackEmpty(&obj->_s2))
{
// 第二个栈的数据有数据,将其全部转移到第一个栈
while(StackSize(&obj->_s2) > 0)
{
// 转移
StackPush(&obj->_s1, StackTop(&obj->_s2));
// 让第二个栈的数据出栈
StackPop(&obj->_s2);
}
}
// 走到这里,如果第二个栈有数据,也全部转移到第一个栈
// 如果第二个栈没有数据,那就直接在第一个栈插入数据就好
StackPush(&obj->_s1, x);
}
int myQueuePop(MyQueue* obj)
{
// 要找到队头(队列开头的元素)就要把全部数据都放在第二个栈,栈顶的数据就是队头
if(!StackEmpty(&obj->_s1))
{
// 第一个栈的数据有数据,将其全部转移到第二个栈
while(StackSize(&obj->_s1) > 0)
{
// 转移
StackPush(&obj->_s2, StackTop(&obj->_s1));
// 让第一个栈的数据出栈
StackPop(&obj->_s1);
}
}
// 由于题目说了一个空的队列不会调用 pop 或者 peek 操作
// 因此这里不用判断两个栈是否为空
// 走到这里数据一定在第二个栈
int ret = StackTop(&obj->_s2);
StackPop(&obj->_s2); // 移除元素
return ret;
}
int myQueuePeek(MyQueue* obj)
{
// 要找到队头(队列开头的元素)就要把全部数据都放在第二个栈,栈顶的数据就是队头
if(!StackEmpty(&obj->_s1))
{
// 第一个栈的数据有数据,将其全部转移到第二个栈
while(StackSize(&obj->_s1) > 0)
{
// 转移
StackPush(&obj->_s2, StackTop(&obj->_s1));
// 让第一个栈的数据出栈
StackPop(&obj->_s1);
}
}
// 由于题目说了一个空的队列不会调用 pop 或者 peek 操作
// 因此这里不用判断两个栈是否为空
// 返回队头,也就是第二个栈的栈顶数据
return StackTop(&obj->_s2);
}
bool myQueueEmpty(MyQueue* obj)
{
// 如果两个栈都为空,队列才是空
return StackEmpty(&obj->_s1) && StackEmpty(&obj->_s2);
}
void myQueueFree(MyQueue* obj)
{
StackDestory(&obj->_s1);
StackDestory(&obj->_s2);
free(obj);
obj = NULL;
}
优化后的代码:
typedef struct
{
Stack _pushST; // 用于插入数据
Stack _popST; // 用于出数据
} MyQueue;
MyQueue* myQueueCreate()
{
// 创建栈
MyQueue* pq = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&pq->_pushST);
StackInit(&pq->_popST);
return pq;
}
void myQueuePush(MyQueue* obj, int x)
{
// 直接把数据插入到pushST栈内
StackPush(&obj->_pushST, x);
}
int myQueuePop(MyQueue* obj)
{
// 这个函数的功能和peek函数的功能就多了一个要移除,也就是让队头数据弹出
// 那我们就考虑让代码复用
int ret = myQueuePeek(obj);
StackPop(&obj->_popST); // 代码复用
return ret;
}
int myQueuePeek(MyQueue* obj)
{
// 要找到队头 也就是popST的栈顶数据
// 要分两种情况,
//1.如果popST栈没有数据,那就把pushST栈的数据转移到popST栈内
//2.如果popST有数据,直接返回栈顶的数据,这个数据就是队头
if(!StackEmpty(&obj->_popST))
{
// popST有数据,直接返回栈顶数据,就是队头
return StackTop(&obj->_popST);
}
else
{
// popST为空,将pushST栈的数据转移到popST栈内
while(!StackEmpty(&obj->_pushST)) // 判断是否为空
{
StackPush(&obj->_popST, StackTop(&obj->_pushST));
StackPop(&obj->_pushST); // 出栈
}
return StackTop(&obj->_popST);
}
}
bool myQueueEmpty(MyQueue* obj)
{
// 如果两个栈都为空,队列才是空
return StackEmpty(&obj->_pushST) && StackEmpty(&obj->_popST);
}
void myQueueFree(MyQueue* obj)
{
StackDestory(&obj->_popST);
StackDestory(&obj->_pushST);
free(obj);
}