栈和队专题刷题
基于双向链表实现双向队列
/* 双向链表节点 */
struct DoubleListNode{
int val; // 节点值
DoubleListNode *next; // 后继节点指针
DoubleListNode *prev; // 前驱节点指针
DoubleListNode(int val) : val(val),prev(nullptr),next(nullptr){
}
};
/* 基于双向链表实现的双向队列 */
class LinkedListDeque
{
private:
DoubleListNode *front,*rear;// 头节点 front ,尾节点 rear
int queSize = 0;// 双向队列的长度
public:
/* 构造方法 */
LinkedListDeque() : front(nullptr),rear(nullptr){
}
/* 析构方法 */
~LinkedListDeque(){
// 遍历链表删除节点,释放内存
DoubleListNode *pre,*cur = front;
while(cur != nullptr){
pre = cur;
cur = cur -> next;
delete pre;
}
}
/* 获取双向队列的长度 */
int size(){
return queSize;
}
/* 判断双向队列是否为空 */
bool isEmpty()
{
return size() == 0;
}
/* 入队操作 */
void push(int num,bool isFront){
DoubleListNode *node = new DoubleListNode(num);
// 若链表为空,则令 front 和 rear 都指向 node
if(isEmpty())
front = rear = node;
// 队首入队操作
else if(isFront){
// 将 node 添加至链表头部
front -> prev = node;
node -> next = front;
front = node;//更新头结点
//队尾入队操作
}else{
// 将 node 添加至链表尾部
rear -> next = node;
node -> prev = rear;
rear = node;//更新尾结点
}
queSize++;// 更新队列长度
}
/* 队首入队 */
void pushFirst(int num) {
push(num, true);
}
/* 队尾入队 */
void pushLast(int num) {
push(num, false);
}
/* 出队操作 */
int pop(bool isFront){
if(isEmpty())
throw out_of_range("队列为空");
int val;
// 队首出队操作
if(isFront){
val = front -> val;// 暂存头节点值
// 删除头节点
DoubleListNode *fNext = front -> next;
if(fNext != nullptr){
fNext -> prev = nullptr;
fNext -> next = nullptr;
}
delete front;
front = fNext; // 更新头节点
// 队尾出队操作
}else{
val = rear -> val;// 暂存尾节点值
// 删除尾节点
DoubleListNode *rPrev = rear -> prev;
if(rPrev != nullptr){
rPrev -> next = nullptr;
rPrev -> prev = nullptr;
}
delete rear;
rear = rPrev;// 更新尾节点
}
queSize--;// 更新队列长度
return val;
}
/* 队首出队 */
int popFirst() {
return pop(true);
}
/* 队尾出队 */
int popLast() {
return pop(false);
}
/* 访问队首元素 */
int peekFirst() {
if (isEmpty())
throw out_of_range("双向队列为空");
return front->val;
}
/* 访问队尾元素 */
int peekLast() {
if (isEmpty())
throw out_of_range("双向队列为空");
return rear->val;
}
/* 返回数组用于打印 */
vector<int> toVector() {
DoublyListNode *node = front;
vector<int> res(size());
for (int i = 0; i < res.size(); i++) {
res[i] = node->val;
node = node->next;
}
return res;
}
};
232.用栈实现队列
用两个栈实现一个队列
class MyQueue {
public:
stack<int> A,B;
MyQueue() {
}
void push(int x) {
A.push(x);
}
int pop() {
//复用peek函数
int peek = this -> peek();
B.pop();
return peek;
}
int peek() {
//当B栈没有元素时,将A栈的元素依次弹出放入B栈
if(B.empty()){
while(!A.empty()){
B.push(A.top());
A.pop();
}
}
int res = B.top();
return res;
}
bool empty() {
return A.empty() && B.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();
*/
225. 用队列实现栈
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
class MyStack {
public:
queue<int> q;
MyStack() {
}
void push(int x) {
q.push(x);
}
int pop() {
int size = q.size();
size--;
// 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾
while(size--){
q.push(q.front());
q.pop();
}
int res = q.front();
q.pop();
return res;
}
int top() {
return q.back();
}
bool empty() {
return q.empty();
}
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
用两个队来实现(其中q2为辅助队)
class MyStack {
public:
queue<int> que1;
queue<int> que2; // 辅助队列,用来备份
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
que1.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int size = que1.size();
size--;
while (size--) { // 将que1 导入que2,但要留下最后一个元素
que2.push(que1.front());
que1.pop();
}
int result = que1.front(); // 留下的最后一个元素就是要返回的值
que1.pop();
que1 = que2; // 再将que2赋值给que1
while (!que2.empty()) { // 清空que2
que2.pop();
}
return result;
}
/** Get the top element. */
int top() {
return que1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return que1.empty();
}
};
20 有效的括号
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(int i = 0;i < s.size();i++){
if(s[i] == '(') st.push(')');
else if(s[i] == '[') st.push(']');
else if(s[i] == '{' ) st.push('}');
else if(st.empty() || st.top() != s[i]) return false;
// st.top() 与 s[i]相等,栈弹出元素
else st.pop();
}
//遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配
return st.empty();
}
};
括号的匹配(z中山大学考研机试题)
在算术表达式中,除了加、减、乘、除等运算外,往往还有括号。
包括有大括号 {}
,中括号 []
,小括号 ()
,尖括号 <>
等。
对于每一对括号,必须先左边括号,然后右边括号;如果有多个括号,则每种类型的左括号和右括号的个数必须相等;对于多重括号的情形,按运算规则,从外到内的括号嵌套顺序为:大括号->中括号->小括号->尖括号,另外相同的括号可以嵌套。
例如,{[()]},{(())},{{}}
为一个合法的表达式,而 ([{}]),{([])},[{<>}]
都是非法的。
输入格式
第一行包含整数 n�,表示共有 n� 个表达式需要判断。
接下来 n� 行,每行包含一个括号表达式。
输出格式
每行输出一个表达式的判断结果。
如果合法输出 YES
,否则输出 NO
。
数据范围
1≤n≤1001≤�≤100
表达式长度不超过 100100。
输入样例:
5
{[(<>)]}
[()]
<>()[]{}
[{}]
{()}
输出样例:
YES
YES
YES
NO
YES
代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
unordered_map<char,int> map;
bool check(string str)
{
stack<int> stk;
for(char c : str)
{
//先获取进栈元素的优先级
int p = map[c];
// //进栈为右括号
if(p > 0)
{
if(stk.empty() || stk.top() != -p) return false;
stk.pop();
}
//进栈为左括号
else
{
if(stk.size() > 0 && abs(p) > abs(stk.top())) return false;
else stk.push(p);
}
}
return stk.empty();
}
int main()
{
//左括号用负数表示,右括号用正数表示,且数值表示其优先级
map['{'] = -4 , map['}'] = 4;
map['['] = -3 , map[']'] = 3;
map['('] = -2 , map[')'] = 2;
map['<'] = -1 , map['>'] = 1;
int T;
cin>>T;
while(T--)
{
string str;
cin>>str;
if(check(str)) puts("YES");
else puts("NO");
}
return 0;
}
https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/
本题也是用栈来解决的经典题目。
那么栈里应该放的是什么元素呢?
我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?
所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。
class Solution {
public:
string removeDuplicates(string S) {
string result;
for(char s : S) {
if(result.empty() || result.back() != s) {
result.push_back(s);
}
else {
result.pop_back();
}
}
return result;
}
};
https://leetcode.cn/problems/evaluate-reverse-polish-notation/
//逆波兰表达式(后缀表达式) 一般用栈来处理(栈的经典应用)
//当碰到运算符就把该运算符前两个值进行运算
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<long long> st;
for(string c : tokens)
{
if(c == "+" || c == "-" || c == "*" || c == "/")
{
long long num1 = st.top();
st.pop();
long long num2 = st.top();
st.pop();
if(c == "+") st.push(num2 + num1);
if(c == "-") st.push(num2 - num1);
if(c == "*") st.push(num2 * num1);
if(c == "/") st.push(num2 / num1);
}else
{
st.push(stoi(c));//stoi 表示 string 转为 int
}
}
int res = st.top();
return res;
}
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans;
deque<int> q; // 双端队列
for (int i = 0; i < nums.size(); i++) {
// 1. 入
while (!q.empty() && nums[q.back()] <= nums[i]) {
q.pop_back(); // 维护 q 的单调性 从后往前移除所有队列中小于等于当前元素的元素
}
q.push_back(i); // 入队
// 2. 出
if (i - q.front() >= k) { // 队首已经离开窗口了
q.pop_front();
}
// 3. 记录答案
if (i >= k - 1) {
// 由于队首到队尾单调递减,所以窗口最大值就是队首
ans.push_back(nums[q.front()]);
}
}
return ans;
}
};
前置知识:优先队列
常见定义举例
//小顶堆
priority_queue<int,vector<int>,greater<int>> q;
//大顶堆
priority_queue<int,vector<int>,less<int>> q;
//默认大顶堆
priority_queue<int> q;
常用函数
top()
pop()
push()
emplace()
empty()
size()
自定义比较方式
当数据类型并不是基本数据类型,而是自定义的数据类型时,就不能用greater或less的比较方式了,而是需要自定义比较方式
在此假设数据类型是自定义的水果:
string fruit
{
string name;
int price;
}
有两种自定义比较方式的方法,如下
1.重载运算符
重载”<”
- 若希望水果价格高为优先级高,则
//大顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.price < f2.price;
}
};
- 若希望水果价格低为优先级高
//小顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.peice > f2.price; //此处是>
}
};
2.仿函数
若希望水果价格高为优先级高,则
//大顶堆
struct myComparison
{
bool operator () (fruit f1,fruit f2)
{
return f1.price < f2.prive;
}
};
//此时优先队列的定义应该如下
priority_queue<fruit,vector<fruit>,myComparison> q;
本题我们就要使用优先级队列来对部分频率进行排序。
为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
此时要思考一下,是使用小顶堆呢,还是大顶堆?
表面看使用大根堆,那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?
所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
class Solution {
public:
//小顶堆
class mycomparison{
public:
bool operator() (const pair<int,int> &lhs,const pair<int,int> &rhs){
//lhs rhs 为map的两个元素,lhs.first是nums里的数key,对应的lhs.second就是出现次数value。
return lhs.second > rhs.second;//小顶堆是大于号
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
//统计元素出现频率
unordered_map<int,int> map;
for(int i = 0;i < nums.size();i++){
map[nums[i]]++;
}
//对频率进行排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> q;
//遍历map中的元素
//若队列元素个数超过k,则将栈顶元素出栈
for(auto &a : map){
q.push(a);
if(q.size() > k){
q.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> res;
for(int i = k - 1;i >= 0;i--){
res.push_back(q.top().first);
q.pop();
}
return res;
}
};