复习的时候,如果想看原题目,直接搜索每题前面的序号,优先在leetcode上搜(leetcode包含了剑指)。
☺☺☺
(1)剑指9.1:用两个栈实现队列(LeetCode225)
不管是用两个栈实现队列,还是用两个队列实现栈,我们都仅在pop时处理,这样方便理解,也方便记忆。
同样的题,再LeetCode和牛客上竟然不一样!!emmm
使用栈实现队列的下列操作:
push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
思路:
push:直接弹入stack1就可以了。
pop:在弹出时,看stack2是否为空,如果为空,就把Stack1中的元素倒到stack2,这时将stack2栈顶元素弹出就行了。
如果不为空,说明之前从stack1过来的元素还没全部弹出,这是接着弹出就行了。
peek:需要先把左边栈stack1的元素倒到右边的栈,再访问右边的栈stack2的栈顶,最后记得访问后再倒回去
class MyQueue {
private:
stack<int> stack1;
stack<int> stack2;
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
stack1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
int a;
if(stack2.empty()){
while(!stack1.empty()){//把栈1元素移到栈2
a=stack1.top();
stack2.push(a);
stack1.pop();
}
}
a=stack2.top();
stack2.pop();
while(!stack2.empty()){//栈2移栈1
stack1.push(stack2.top());
stack2.pop();
}
return a;
}
/** Get the front element. */
int peek() {
while(!stack1.empty()){
stack2.push(stack1.top());
stack1.pop();
}
int cur;
cur = stack2.top();
while(!stack2.empty()){//再移回去
stack1.push(stack2.top());
stack2.pop();
}
return cur;
}
/** Returns whether the queue is empty. */
bool empty() { //因为最后都移到了栈1,所以只需要判断栈1是否为空
return stack1.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
☺☺☺
(2)剑指9.2:用两个队列实现栈(LC232)
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
**两个队列的实现 仅在pop时处理
1、pop时,最后一个元素之前的元素全部出队,放到临时队列
2、pop最后一个元素
3、再将临时队列中的元素全部移回来
**
class MyStack {
private:
queue<int> queue1;
queue<int> queue2;
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
queue1.push(x);//插入队列
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
while(queue1.size()>1){//将前size-1个元素移至中转队列
queue2.push(queue1.front());
queue1.pop();
}
int a=queue1.front();
queue1.pop();
while(!queue2.empty()){//再将中转队列中的所有元素移回队列
queue1.push(queue2.front());
queue2.pop();
}
return a;
}
/** Get the top element. */
int top() {
return queue1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return queue1.empty();
}
};
☺☺☺
(3)剑指30:包含min函数的栈(LC155)
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素
思路:用两个栈,一个栈存数据,一个栈存各阶段最小数
class MinStack {
private:
stack<int> s; //本栈
stack<int> stmp; //辅助栈
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
s.push(x); //先把值压入本栈
if(stmp.empty() || x < stmp.top()){ //每次保证最小的值到stmp
stmp.push(x);
}
else{
stmp.push(stmp.top()); //保证stmp中和s中元素个数相同,这样pop时就可以同时pop
}
}
void pop() {
stmp.pop();
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return stmp.top(); //栈顶元素为当前最小数,因为它一直保存的最小值
}
};
☺☺☺
(4)剑指31:栈的压入、弹出序列 【中等】(LC946)
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
思路:借助一个辅助栈。看图很容易理解。
一直往栈中压入元素,直到栈顶元素和出栈序列中的第一个元素相等,将栈顶元素弹出。这样循环等压栈序列遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped){
if(pushed.empty() && popped.empty()) return true; //两个空栈返回true
stack<int> s;
int j=0; //标记poped中元素的位置
for(int i=0; i<pushed.size(); i++){//扫描入栈序列
s.push(pushed[i]);//push元素直到栈顶元素为pop序列中的元素
while(!s.empty()&& s.top()==popped[j]){ //相等时,辅助栈出栈,弹出序列索引加一
s.pop();
j++;
}
}
if(s.empty()){ //如果为空,则说明入栈序列和出栈序列相匹配,为true,否则为false
return true;
}
else{
return false;
}
}
};
☺☺☺
(5)剑指59.1:滑动窗口的最大值 (LC239)
这题看了半天,还好我没放弃。原来是hard
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置(下标) 最大值
0 1 2 3 4 5 6 7
------------------------------------
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
法1
【核心思想】
用双向队列存储数组的下标**
【数据结构】
双向队列
【思路】
第一步: 实时更新数字最大的下标
第二步: 实时判断当前数字最大的小标是否在滑动窗口内部 ,i 始终为滑动窗口的头
第三步: 压入下标
第四步: 当滑动窗口的元素满足要求,即可压入元素
或理解:
用双向队列存储数组的下标,每一轮进行移动窗口、维护和输出的动作
每一轮使最大的数对应的下标在双向队列的最左端,如果双向队列中“左边下标对应的元素”要小于“右边下标对应的元素”,那么就把左边的元素进行清除维护,输出双向队列最左端下标对应的元素**
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>result; //存放结果的vector
deque<int>temp; //记录最大数字小标的双向队列
int length=nums.size();
for(int i=0;i<length;i++)
{
while(!temp.empty()&&nums[i]>nums[temp.back()]){ //如果队列尾部的小标所应对的元素小于当前元素,就把队列尾部的下标删除
temp.pop_back();
}
//i+1-k表示退后三部的元素的下标
if(!temp.empty()&& temp.front()< i-k+1){ //看队列首下标左边是否超出了3之外(是否出了窗口之外)
temp.pop_front();
}
temp.push_back(i); //把当前元素下标放入队列
if(i-k+1>=0) //if (i >= k -1) // i>=下标2,说明构成一个窗口大小了
result.push_back(nums[temp.front()]) ;
}
return result;
}
};
法2:最容易想到的笨办法。
一个i指示每个窗口的起点,在i循环内j遍历该窗口找出最大值。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res; //存放结果
if(nums.empty()) return res; //必须写
int len=nums.size();
for(int i=0; i<=len-k; i++){ //定位每个窗口的第一个元素
int max=nums[i];
for(int j=i; j<i+k; j++){ //在每个窗口中找最大值
if(nums[j]>max){
max=nums[j];
}
}
res.push_back(max);
}
return res;
}
};
☺☺☺
(6)剑指59.2:队列的最大值 【中等】
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
示例 1:
输入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:
输入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
解题思路
使用一个数组记录数据
使用一个双端队列维护一个单调递减的队列,数组头部就是当前最大值
用两个队列来完成任务
【数据结构】队列与双向队列
【思路】
用一个队列保存正常元素**,另一个双向队列保存单调递减的元素
入栈时,第一个队列正常入栈;第二个队列是递减队列,所以需要与之前的比较,从尾部把小于当前value的全部删除(因为用不到了)
出栈时,第一个队列正常出栈;同时与第二个队列的头部值作比较,如果相同,那么一起出栈**
class MaxQueue {
public:
queue<int> que; //普通队列
deque<int> deq; //双向队列 double queue方便记忆
MaxQueue() {
}
int max_value() {
if (deq.empty()) {
return -1;
}
return deq.front();
}
void push_back(int value) {
while (!deq.empty() && deq.back() < value) {//反正是要插入的,但是如果前面的值小于当前就要去掉
deq.pop_back(); //保证最大值队列中存的是主极大与次极大
}
deq.push_back(value);
que.push(value);
}//插入的时候就不要考虑删除时候的事情
int pop_front() {
if (que.empty()) {
return -1;
}
int ans = que.front();
if (deq.front() == ans) { //如果弹出的元素即为当前最大值,注意把最大值队列队首元素也出队。这样才能知道下一个最大值
deq.pop_front();
}
que.pop();
return ans;
}
};
比较:
min栈的实现:队列中是尾部插入,首部删除(滑动窗口是这种情况,故用队列较好),而min栈较方便,在一端插入和删除
最大值队列在push当前值之前,需将之前较小的值出队处理(从队尾开始判断),队首元素即为当前队列的最大值
知识点:deque 与 queue
deque 顺序容器。
deque中的元素可以通过位置下标来访问。支持快速的随机访问。deque各方面都与vector类似,唯一的差别是:deque支持在容器的头部和尾部快速插入和删除(所以加双向队列),而且在两端插入和删除元素都不会导致重新分配空间。
queue 队列。尾部插入,头部删除。