一、有效的括号 – leetcode
描述:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例1:
输入: “()”
输出: true
示例2:
输入: “()[]{}”
输出: true
示例3:
输入: “(]”
输出: false
分析:
这道题是一个括号匹配问题, 之前有做过类似的题目, 不过那道题的括号类型只有一种,使用一个计数器就可以搞定。 这道题不同, 括号类型的匹配有多种。 因此我们需要使用到栈,我们遍历给我们的字符串, 所有关于左括号(’[’, ‘{’, ‘(’)放到栈中, 当遇到右括号时(’]’,’}’,’)’)时, 我们会取出栈顶的一个元素和它进行匹配, 如果匹配成功, 则从栈中pop掉这个左括号。 如果没有匹配成功, 则直接返回false。直到读取完给我们的字符串。
这里需要考虑几个问题。
1、第一个元素是右括号,则这个右括号无法匹配, 直接返回false.
2、当遍历完字符串, 栈里面的左括号要和右括号匹配消耗完, 如果最终栈不为空, 则返回false, 如果为空,返回true。
源码:
//判断是否匹配函数
bool IsMatch(const char& ch, const char& st) {
if(ch == '(' && st == ')') {
return true;
}
if(ch == '{' && st == '}') {
return true;
}
if(ch == '[' && st == ']') {
return true;
}
return false;
}
bool isValid(string s) {
stack<char> st; //创建读取左括号栈
for(const auto& it : s) {
if(it == '(' || it == '{' || it == '[') { //为左括号类型时放到栈里面
st.push(it);
}
else { //为右括号类型则进行匹配判断
if(st.empty()) { //第一个元素为右括号类型
return false;
}
char ch = st.top(); //栈顶左括号元素
if(IsMatch(ch, it)) { //匹配函数
st.pop(); //匹配成功, 出队
}
else { //匹配失败
return false;
}
}
}
if(st.empty()) { //最终判断栈是否为空
return true;
}
else //栈不为空
return false;
}
测试结果:
二、用队列实现栈 – leetcode
描述:
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
分析:
这里我会使用c++去写, 使用STL标准库。 在这里我先提前说一下STL库中queue适配器。STL提供的接口有push,pop, front, back, swap这几个接口等。 这几个接口我会用到。
采用队列实现栈我会使用两个队列去实现, 一个主队列负责存储数据, 另一个辅助队列倒数据(就像两个杯子倒水一样), 另一个队列是为了实现栈的pop操作。(因为队列和栈的性质不行, 需要借助一些思想去做)
1、push; 就相当于主队列的push操作。
2、pop; 将主队列中的数据倒入辅助队列当中, 主队列只留下最后一个数据不倒入(最后一个数据不倒入,就相当于pop掉),其他数据都倒入辅助队列。然后调用swap交换两个队列
3、top; 等价于主队列back接口
4、empty; 等价于判断主队列empty。
源码:
class MyStack {
public:
/** Initialize your data structure here. */
MyStack() {
//没什么可写的
}
/** Push element x onto stack. */
void push(int x) {
hq.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
if(hq.empty()) {
return -1;
}
int front;
while(!hq.empty()) {
front = hq.front();
hq.pop();
if(hq.empty()) {
break;
}
tq.push(front);
}
hq.swap(tq); //使主队列hq一直不为空
return front;
}
/** Get the top element. */
int top() {
return hq.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return hq.empty();
}
private:
queue<int> hq;
queue<int> tq;
};
测试结果:
三、用栈实现队列 – leetcode
描述:
使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例1:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
分析:
STL中栈的接口有push、pop、top、empty等接口, 我会使用这些接口去实现队列。同样我们需要两个栈去实现一个队列。 一个主栈负责接收数据, 另一个辅助栈负责倒数据。对于队列的pop、feek操作来说是比较困难的, 其他操作是很简单的。
1、push; 等价于主栈push操作
2、pop; 我们会将主栈的数据倒入辅助栈中, 此时辅助栈的栈顶元素就是我们需要pop的元素, pop辅助栈。 最后将辅助栈的数据倒入主栈。
3、peek; 和pop操作相同, 不过它不会pop辅助栈的栈顶元素, 而是返回辅助栈的栈顶元素。
4、empty; 等价于判断主栈的empty。
源码:
class MyQueue {
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
is.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
if(is.empty()) {
return -1;
}
while(!is.empty()) {
int top = is.top();
is.pop();
os.push(top);
}
int top = os.top();
os.pop();
while(!os.empty()) {
int top = os.top();
os.pop();
is.push(top);
}
return top;
}
/** Get the front element. */
int peek() {
if(is.empty()) {
return -1;
}
while(!is.empty()) {
int top = is.top();
is.pop();
os.push(top);
}
int front = os.top();
while(!os.empty()) {
int top = os.top();
os.pop();
is.push(top);
}
return front;
}
/** Returns whether the queue is empty. */
bool empty() {
return is.empty();
}
private:
stack<int> is;
stack<int> os;
};
测试结果:
四、最小栈 – leetcode
描述:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例1:
输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
分析:
做这道题我们会定义两个栈, 一个main栈是正常存放数据, 另一个min栈是存放最小值数据。
main栈存储原则我就不用多说, push操作。
min栈存储原则:
1、当min栈为空时, 会一直存放数据。
2、每当存放一个新的数据val, 先判断val是否<=min栈的栈顶元素,如果是则将该元素存放倒min栈中, 如果不是则不存放。
3、当主栈pop的时候, 需要判断主栈pop的元素(也就是未pop之前的栈顶元素)是否等于min栈的栈顶元素。 如果相等, min栈pop, 不相等,则不需要pop。
下面讲解一下这些接口的对应实现。
1、push; main栈直接push, min栈根据上述的原则push。
2、pop; main栈直接pop, min栈根据上述原则pop。
3、top; 返回main栈的栈顶元素。
4、getMin; 返回min栈的栈顶元素。
源码:
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
mainstack.push(x);
if(minstack.empty()) {
//直接插进来
minstack.push(x);
}
else if(x <= minstack.top()) {
minstack.push(x);
}
}
void pop() {
if(mainstack.empty()) {
return;
}
int top = mainstack.top();
mainstack.pop();
if(top == minstack.top()) {
minstack.pop();
}
}
int top() {
return mainstack.top();
}
int getMin() {
return minstack.top();
}
private:
stack<int> mainstack;
stack<int> minstack;
};
测试结果:
五、设计循环队列 – leetcode
描述:
设计一个循环队列, 满足下面的接口需要。
1、 MyCircularQueue(k): 构造器,设置队列长度为 k 。
2、Front: 从队首获取元素。如果队列为空,返回 -1 。
3、Rear: 获取队尾元素。如果队列为空,返回 -1 。
4、enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
5、deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
6、sEmpty(): 检查循环队列是否为空。
7、isFull(): 检查循环队列是否已满。
分析:
这道题就是考察队循环队列的设计, 如果你懂这个设计就很简单, 如果不懂就比较难了。 这道题和我上一篇博客中设计循环队列思想一样。
我在这里就只讲一些重点,不再多叙述, 感兴趣可以看上一篇的博客。
1、开辟空间问题。 题目中给我的长度为k, 实际上是让我们使用定长的容器去做, 我建议使用定长的数组去做。 在开辟空间的时候, 我们会多开辟一个空间也就是k+1大小的空间。 这样做方便设计。
2、队满问题; 既然是定长, 就会存在队满的问题, 判断队满的条件就是:
(rear + 1) % (k + 1) == front。
3、队空问题: 队空条件: rear == front;
4、循环队列; 我们的下标rear、front在++操作后, 需要注意是否越界的情况。
基于这些问题我们就可以很简单写出一个循环队列。
源码:
class MyCircularQueue {
public:
/** Initialize your data structure here. Set the size of the queue to be k. */
MyCircularQueue(int k) {
date = new int[k + 1];
front = rear = 0;
n = k;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
bool enQueue(int value) {
if(isFull()) {
return false;
}
date[rear] = value; //插入, rear++
rear++;
if(rear == n + 1) { //rearu越界, 重置
rear = 0;
}
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
bool deQueue() {
if(isEmpty()) {
return false;
}
front++; //删除, 右移
if(front == n + 1) { //front越界, 重置
front = 0;
}
return true;
}
/** Get the front item from the queue. */
int Front() {
if(isEmpty()) {
return -1;
}
return date[front];
}
/** Get the last item from the queue. */
int Rear() {
if(isEmpty()) {
return -1;
}
if(rear == 0) {
return date[n];
}
else
return date[rear - 1];
}
/** Checks whether the circular queue is empty or not. */
bool isEmpty() {
return rear == front;
}
/** Checks whether the circular queue is full or not. */
bool isFull() {
return ((rear + 1) % (n + 1)) == front;
}
private:
int* date;
int front;
int rear;
int n;
};
测试结果:
好啦, 本章介绍完毕, 如果您正在准备数据结构方面的面试或者考试, 希望本章的例题会让你更加了解栈、队列。 本章的例题如果你用心做一遍, 相信一定会有很大的收获的。 当然如果您都会, 此篇博客也可以不错复习, 正所谓谦卑学习, 温故而知新嘛。 谢谢!