目录
预备知识
栈
栈的原理
栈的STL实现
队列
队列的原理
队列的STL实现
使用队列实现栈(Easy)
LeetCode 225.Implement Stack using Queues
设计一个栈,支持如下操作,这些操作的算法复杂度需要是常数级,O(1),栈的内部存储数据的结构为队列,队列的方法只能包括push、peek(front)、pop、size、empty等标准的队列方法。
总体思路
队列的特定是"先进先出",栈的特定是"先进后出",那么,想用一个"先进先出"的结构去实现一个"先进后出"的结构,就是本题的关键,最理想的做法就是通过一种方式,是将压入队列的数据逆序排列,这样的话,对队列的操作数据的时候,和对栈操作数据的方式就一样了,那该怎样实现呢?最笨的方法,弄俩队列,先把数据压入到一个队列里面,再把这个队列里的内容逐个取出来,再压入到另外一个队列中去,那另外一个队列中的数据排列,就成了逆序排列了。
细节设计
创建一个临时队列,来作为将队列中的元素逆序排列的中转工具。
代码实现
#include <stdio.h>
#include <queue>
class MyStack {
public:
MyStack() {
}
void push(int x) {
std::queue<int> temp_queue;
temp_queue.push(x);
while(!_data.empty()){
temp_queue.push(_data.front());
_data.pop();
}
while(!temp_queue.empty()){
_data.push(temp_queue.front());
temp_queue.pop();
}
}
int pop() {
int x = _data.front();
_data.pop();
return x;
}
int top() {
return _data.front();
}
bool empty() {
return _data.empty();
}
private:
std::queue<int> _data;
};
int main(){
MyStack S;
S.push(1);
S.push(2);
S.push(3);
S.push(4);
printf("%d\n", S.top());
S.pop();
printf("%d\n", S.top());
S.push(5);
printf("%d\n", S.top());
return 0;
}
经验总结
-
两个队列可以实现一个栈,两个栈可以实现一个队列。
使用栈实现队列(Easy)
LeetCode 232.Implement Queue using Stacks
设计一个队列,队列支持如下操作,这些操作的算法复杂度需要是常数级,O(1),队列的内部存储数据的结构为栈,栈的方法只能包括push、top、pop、size、empty等标准的栈方法。
总体思路
用一个数据栈("先进后出")实现一个队列("先进先出"),依然是利用临时栈来调换元素的次序,来实现,这道题和上一道题是类似的。
细节设计
代码实现
#include <stdio.h>
#include <stack>
class MyQueue {
public:
MyQueue() {
}
void push(int x) {
std::stack<int> temp_stack;
while(!_data.empty()){
temp_stack.push(_data.top());
_data.pop();
}
temp_stack.push(x);
while(!temp_stack.empty()){
_data.push(temp_stack.top());
temp_stack.pop();
}
}
int pop() {
int x = _data.top();
_data.pop();
return x;
}
int peek() {
return _data.top();
}
bool empty() {
return _data.empty();
}
private:
std::stack<int> _data;
};
int main(){
MyQueue Q;
Q.push(1);
Q.push(2);
Q.push(3);
Q.push(4);
printf("%d\n", Q.peek());
Q.pop();
printf("%d\n", Q.peek());
return 0;
}
经验总结
- 两个队列可以实现一个栈,两个栈可以实现一个队列。
包含min函数的栈(Easy)
LeetCode 155.MinStack
设计一个栈,支持如下操作,这些操作的算法复杂度需要是常数级O(1):
- push(x):将元素x压入栈中
- pop():弹出(移除)栈顶元素
- top():返回栈顶元素
- getMin():返回栈内最小元素
总体思路
本题的难点是实现算法复杂度是O(1)级别的,那么,肯定不能用创建临时对象的方式,通过遍历来实现,因为这些操作的复杂度都是O(N)级别的。
用一个变量来记录最小值?(行不通)
那能不能用一个变量来记录栈中的最小值呢?假设创建一个存放最小值的变量min,变量栈中的每个元素,出现比存放在m中的变量更小的值,就把这个值放到min中去,然后,继续遍历,但按照这种方式去遍历的话有个弊端,如下图所示过程:
当栈中只有一个值"-2"的时候,通过getMin()可以获得栈中的最小值是"-2",这没问题;
开始往栈中填入一个"0"的时候,去拿新填入的值和原来的min值"-2"去比,发现还是"-2"更小,所以min不变;
同理,当再在栈中填入一个"-5"的时候,拿"-5"跟"-2"比,"-5"是更小的,所以,把min值更替为"-5";
但是,当将栈中的"-5"给弹出到栈外之后,如何对min值进行更新呢?这个时候最小值"-2"是被压在栈底的,根本无法在时间复杂度O(1)的前提下用一个变量去获取弹出"-5"后栈中剩下的元素的最小值。可见这种方式是行不通的!
总结一下:
- 一个变量min是无法完成记录栈中所有状态下的最小值的。
- 栈的每个状态,都需要有一个变量记录最小值。
用另一个栈,存储各个状态最小值(行得通)
这种思路的核心是,无论我们怎么的push,pop,我们专门新开一个栈,去专门同步(一一对应)的存储栈的每个状态下的最小值。
细节设计
用另一个栈,存储各个状态最小值
代码实现
#include <stdio.h>
#include <stack>
class MinStack {
public:
MinStack() {
}
void push(int x) {
_data.push(x);
if (_min.empty()){
_min.push(x);
}
else{
if (x > _min.top()){
x = _min.top();
}
_min.push(x);
}
}
void pop() {
_data.pop();
_min.pop();
}
int top() {
return _data.top();
}
int getMin() {
return _min.top();
}
private:
std::stack<int> _data;
std::stack<int> _min;
};
int main(){
MinStack minStack;
minStack.push(-2);
printf("top = [%d]\n", minStack.top());
printf("min = [%d]\n\n", minStack.getMin());
minStack.push(0);
printf("top = [%d]\n", minStack.top());
printf("min = [%d]\n\n", minStack.getMin());
minStack.push(-5);
printf("top = [%d]\n", minStack.top());
printf("min = [%d]\n\n", minStack.getMin());
minStack.pop();
printf("top = [%d]\n", minStack.top());
printf("min = [%d]\n\n", minStack.getMin());
return 0;
}
经验总结
- 栈的每个状态,都需要有一个变量专门记录。而且,应该专门开一个栈来存放记录栈状态的变量,以达到与被记录栈的同步操作,时间复杂度为常量O(1),开销最小。
合法的出栈序列
poj1363
已知从1至n的数字序列,按顺序入栈,每个数字入栈后即可出栈,也可在栈中停留,等待后面的数字入栈出栈后,该数字再出栈,求该数字序列的出栈序列是否合法?(Medium)
总体思路
使用栈与队列栈去模拟操作,只要模拟的入栈出栈操作,与给定的是一致的,那就证明是合法的。那么难点来了,如何模拟直接入栈出栈是容易的,但如何模拟在栈中停留呢?这就需要队列了,来记录栈中每个时刻的状态了。
细节设计
代码实现
#include <stdio.h>
#include <stack>
#include <queue>
bool check_is_valid_order(std::queue<int> &order){
std::stack<int> S;
int n = order.size();
for (int i = 1; i <= n; i++){
S.push(i);
while(!S.empty() && order.front() == S.top()){
S.pop();
order.pop();
}
}
if (!S.empty()){
return false;
}
return true;
}
int main(){
int n;
int train;
scanf("%d", &n);
while(n){
scanf("%d", &train);
while (train){
std::queue<int> order;
order.push(train);
for (int i = 1; i < n; i++){
scanf("%d", &train);
order.push(train);
}
if (check_is_valid_order(order)){
printf("Yes\n");
}
else{
printf("No\n");
}
scanf("%d", &train);
}
printf("\n");
scanf("%d", &n);
}
return 0;
}
经验总结
- 此题和上一题也是类似的,关键在于记录栈每次操作的状态,这次用队列来记录。而且,此题比上一题难在多了一种栈的状态(在栈中停留)。
简单的计算器(Hard)
LeetCode 224.Basic Calculator
设计一个计算器,输入一个字符串存储的数学表达式,可以计算包括"("、")"、"+"、"-"四种符号的数字表达式,输入的数字表达式字符串保证是合法的。输入的数字表达式中可能存在空格字符。
总体思路
之所以称为简单的计算器,是因为只涉及加减两种运算,和括号操作,那么,就需要开始分析了,在这道题里面需要分两步:1)判断字符是否合法; 2)进行运算。
1)判断合不合法,其实就是看输入的字符串是数字还是规定的加减括号这四种运算符,如果不是,就不是合法的字符,同时,还需要注意的是,要把输入的字符串数字变成整型类型数字才可以计算。
2)在计算的时候,由于有括号的存在,需要实现优先级的操作,这就用到了栈,通过两个栈,一个存数字一个存运算符,再设定一个标记符,来决定当前的运算顺序。
细节设计
1)判断字符是否合法
2)进行运算,用栈实现优先级运算
代码实现
#include <stdio.h>
#include <string>
#include <stack>
class Solution {
public:
int calculate(std::string s) {
static const int STATE_BEGIN = 0;
static const int NUMBER_STATE = 1;
static const int OPERATION_STATE = 2;
std::stack<int> number_stack;
std::stack<char> operation_stack;
int number = 0;
int STATE = STATE_BEGIN;
int compuate_flag = 0;
for (int i = 0; i < s.length(); i++){
if (s[i] == ' '){
continue;
}
switch(STATE){
case STATE_BEGIN:
if (s[i] >= '0' && s[i] <= '9'){
STATE = NUMBER_STATE;
}
else{
STATE = OPERATION_STATE;
}
i--;
break;
case NUMBER_STATE:
if (s[i] >= '0' && s[i] <= '9'){
number = number * 10 + s[i] - '0';
}
else{
number_stack.push(number);
if (compuate_flag == 1){
compute(number_stack, operation_stack);
}
number = 0;
i--;
STATE = OPERATION_STATE;
}
break;
case OPERATION_STATE:
if (s[i] == '+' || s[i] == '-'){
operation_stack.push(s[i]);
compuate_flag = 1;
}
else if (s[i] == '('){
STATE = NUMBER_STATE;
compuate_flag = 0;
}
else if (s[i] >= '0' && s[i] <= '9'){
STATE = NUMBER_STATE;
i--;
}
else if (s[i] == ')'){
compute(number_stack, operation_stack);
}
break;
}
}
if (number != 0){
number_stack.push(number);
compute(number_stack, operation_stack);
}
if (number == 0 && number_stack.empty()){
return 0;
}
return number_stack.top();
}
private:
void compute(std::stack<int> &number_stack,
std::stack<char> &operation_stack){
if (number_stack.size() < 2){
return;
}
int num2 = number_stack.top();
number_stack.pop();
int num1 = number_stack.top();
number_stack.pop();
if (operation_stack.top() == '+'){
number_stack.push(num1 + num2);
}
else if(operation_stack.top() == '-'){
number_stack.push(num1 - num2);
}
operation_stack.pop();
}
};
int main(){
std::string s = "1+121 - (14+(5-6) )";
Solution solve;
printf("%d\n", solve.calculate(s));
return 0;
}
经验总结
- 栈可以模拟计算操作,将数值与运算符分别存放。
- 判断字符串是数字和操作符的方式可以借鉴。
- 通过设置标记值来确定优先级。