1. JZ9 用两个栈实现队列(易)
1. 官方和自己(推荐)
push操作就直接往stack1中push, pop操作需要分类一下:如果stack2为空,那么需要将stack1中的数据转移到stack2中,然后在对stack2进行pop,如果stack2不为空,直接pop就ok。
复杂度:时间复杂度push和pop均为为
O
(
1
)
O(1)
O(1),空间复杂度为
O
(
n
)
O(n)
O(n)。
//push操作就直接往stack1中push,
// pop操作需要分类一下:如果stack2为空,那么需要将stack1中的数据转移到stack2中,然后在对stack2进行pop,
// 如果stack2不为空,直接pop就ok。
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
int res=-1;
//1倒入2
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
}
res = stack2.top();
stack2.pop();
return res;
}
private:
stack<int> stack1; //插入栈,入队
stack<int> stack2; //删除栈,出队
};
2. 本题小结
- 双栈一个管理队头,一个管理队尾,当队头为空但又需要pop的时候,将队尾中的所有元素倒过来再pop()
2. JZ73 翻转单词序列(易)
1. 自己的栈(推荐)
class Solution {
public:
string ReverseSentence(string str) {
string res="";
if(str.length()==0) return res;
stack<string> sta;
string tmp="";
for(int i=0,j=0; i<str.length(); ++i){
if(!isspace(str[i]) ) //非空格
{
tmp+=str[i];
if(i==str.length()-1 && tmp!="")
sta.push(tmp);
}
else if(tmp!=""){
sta.push(tmp);
tmp="";
}
}
while(!sta.empty()){
res+=sta.top();
sta.pop();
if(!sta.empty()) res+=" ";
}
return res;
}
};
2. 官方
有个方法是两次反转,但是这可以用栈找到一个单词之后单独反转reverse(str.begin(), str.end())
3. 本题小结
- 利用栈实现翻转,好理解。
3. JZ30 包含min函数的栈(中)
1. 官方双栈法(推荐)
思考:之前自己想过维护一个最小值变量,当调用min()函数时就返回这个值,但是如果最小值被pop()了,怎么找到下一个最小值呢?继续想,可不可以管理多个最小值,一个值弹出之后,对应的把此时的最小值也弹出,于是引入辅助栈,每次push数据到数据栈ataSta中同时也将当时对应的min值push到MinSta中,pop时同时pop数据栈和辅助栈,就完成了带min的栈的数据结构。
class Solution {
public:
stack<int> DataSta;
stack<int> MinSta;
//数据栈入栈,最小栈比较入栈最小值
void push(int value) {
DataSta.push(value);
if(MinSta.empty()) MinSta.push(value);
else MinSta.push(MinSta.top()<=value ? MinSta.top():value);
}
void pop() {
if(DataSta.empty())
throw std::exception(std::logic_error{"Stack is empty."});
else{
DataSta.pop();
MinSta.pop();
}
}
int top() {
return DataSta.top();
}
int min() {
return MinSta.top();
}
};
2. 本题小结
- 使用最小栈MinSta来管理每个push操作之后当时的最小值。
4. JZ31 栈的压入、弹出序列(中)
1. 官方辅助栈
时间和空间复杂度均为
O
(
n
)
O(n)
O(n)
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
int Size = pushV.size();
stack<int> sta;
for(int i=0,j=0;i<Size; ++i){
//保证栈不空,到底的情况会在下面处理
if(sta.empty()) sta.push(pushV[j++]);
//按照入栈顺序压入栈,直到找到该出栈的元素,如果全部入了还是没找到该出的,就证明不是
while(sta.top()!=popV[i]){
if(j==Size) return false;
sta.push(pushV[j++]);
}
sta.pop();
}
return true;
}
};
2. 原地操作(推荐)
时间复杂度为
O
(
n
)
O(n)
O(n),空间复杂度
O
(
1
)
O(1)
O(1),原地操作,无序额外空间。
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
int n=0; //栈空间大小
int j=0; //出栈序列下标
//对于每个待入栈的元素
for(int num:pushV)
{
pushV[n] = num ;
//当栈不为空且栈顶等于当前出栈序列
while(n>=0 && pushV[n]==popV[j]){
//出栈,减小栈空间
++j;
--n;
}
++n;
}
//如果最后栈为空则证明全部出去了,是对的
return n==0;
}
};
3. 本题小结
- 使用辅助栈来压入待出的元素,直到找到带出的元素,如果找不到,说明
false
- 原地操作(需要征求面试官是否能原地操作),
pushV
的前半部分没用了,可以当作栈来使用,最后如果栈都空了,说明true
,否则false
5. JZ59 滑动窗口的最大值(难)
1. 官方双向队列(推荐)
思路就是只把有可能成为窗口最大值的数据推到双向队列中,双向队列的队首deq.front()
存放的时是该窗口中最大元素的下标。
- 首先处理第一个窗口,不会越界,如果后面进来的数据比前面的大,那么前面的数据不可能是最大值,如果新数据 a a a比队尾数据小,但是是有可能成为最大值的(当后续所有值都比 a a a)小时,a就是最大值。
- 在后面的窗口中,可能存在越界的情况,判断队首是否越界,若越界,则
pop_front()
,若未越界,则判断新值 b b b是否大于队尾的下标对应的数,循环pop_back()
队尾下标对应的值小于 b b b的下标,然后把 b b b的下标 p u s h b a c k ( ) push_back() pushback(),每个窗口处理结束之后往Res中push_back(nums(deq.front()))
class Solution {
public:
//仿照带有min函数的栈,引入辅助栈MaxSta,每次移动之后将最大值insert到vector<int>中
vector<int> maxInWindows(const vector<int>& nums, int size) {
vector<int> Res; //保存最大值
deque<int> deq;
//没必要使用队列来实现窗口移动
//处理第一个窗口
for(int i=0; i<size; ++i){
while(!deq.empty() && nums[deq.back()]<=nums[i] ){
deq.pop_back();
}
deq.push_back(i);
}
Res.push_back(nums[deq.front()]);
//处理
for(int i=size; i<nums.size(); ++i){
//超过窗口范围的下标直接pop_front,由于每次只移动一个,所以只需要判断最开始的越没越界即可,不用循环
if(!deq.empty() && deq.front()<i-size+1)
deq.pop_front();
//尾部小于新值的都pop_back()
while(!deq.empty() && nums[deq.back()]<=nums[i])
deq.pop_back();
deq.push_back(i); //推入新下标
//推入的是最大的或者比队尾小的
Res.push_back(nums[deq.front()]);
}
return Res;
}
};
2. 双栈实现队列+含有max()函数的栈
这两种题之前都做过,可以结合起来实现 O ( n ) O(n) O(n)的算法,但是实现比较复杂。
3. 官方暴力解法
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
vector<int> res;
//窗口大于数组长度的时候,返回空
if(size <= num.size() && size != 0)
//数组后面要空出窗口大小,避免越界
for(int i = 0; i <= num.size() - size; i++){
//寻找每个窗口最大值
int max = 0;
for(int j = i; j < i + size; j++){
if(num[j] > max)
max = num[j];
}
res.push_back(max);
}
return res;
}
};
3. 本题小结
-
挺烧脑的,看看书 P 289 − 291 P_{289-291} P289−291。
-
当然可以使用暴力解法,但是没意义了。