本文用于记录个人算法竞赛学习,仅供参考
目录
一.模拟单链表
二.双链表
三.栈
四.单调栈
五.队列
六.循环队列
七.单调队列
为什么要用数组模拟而不用现成的STL,因为用数组模拟效率会快一点,比如用结构体+指针的方式创建链表,new的效率会很慢,极端情况可能会超时,所以有时候需要用数组模拟。
一.模拟单链表
用数组模拟单链表,需要两个数组,一个数据域,一个指针域;head记录链表的起始位置;index用于记录当前已经用到哪个点(用于创建节点),index一直向后走,不会走回头路,这里的index相当于每个节点的地址,每个节点的地址都独一无的。
模板:
//head 存储链表头节点 e[] 存储节点的值 ne[] 存储节点的next指针
//index 记录当前已经用到了哪个节点,相当于地址,用于创建节点
const int N = 100010;//看情况给大小
int head, e[N], ne[N], index;
//初始化
void init()
{
head = -1;//表示空集
index = 0;
}
//在链表头插入一个数a--相当于创建一个数据为a的节点头插链表
void insert(int a)
{
e[index] = a;
ne[index] = head;
head = index++;//记得index要向后走
}
//头删
void remove()
{
//不存在节点
if (head == -1)
return;
head = ne[head];
}
//插到下标k后面//如果向插到下标k前面
void insert_k(int k, int a)
{
e[index] = a;
ne[index] = ne[k];
ne[k] = index++;
}
//将下标k后面的点删除
void remove_k(int k)
{
ne[k] = ne[ne[k]];
}
//遍历
void ergodic()
{
for (int i = head; i != -1; i = ne[i])
cout << e[i] << ' ';
}
二.双链表
双链表比单链表多了一个指针域
模板:
// e[] 存储节点的值 l[] 存储节点的pre指针 r[]存放节点的next指针
//index 记录当前已经用到了哪个节点,相当于地址,用于创建节点
const int N = 100010;//看情况给大小
int e[N], l[N], r[N], index;
//初始化
void init()
{
//0是head指针,1是tail指针, 0头节点右指针指向1尾节点,1尾节点的左指针指向0头节点,形成闭环
r[0] = 1, l[1] = 0;
index = 2;//要从2开始了
}
//在节点k右边插入一个数a//在k左边插入一个数a 即为 insert(l[k], a);
void insert(int k, int a)
{
e[index] = a;
l[index] = k;
r[index] = r[k];
l[r[k]] = index;
r[k] = index++;
}
//删除节点k
void remove(int k)
{
l[r[k]] = l[k];
r[l[k]] = r[k];
}
三.栈
栈是先进后出的数据结构,只需要维护栈顶就可以了
模板:
const int N = 100010;
//栈
int st[N];
//tt维护栈顶,tt == 0时是栈底,栈底不存数据
int tt = 0;
//插入
void insert(int a)
{
st[++tt] = a;
}
//删除
void remove()
{
tt--;
}
//栈顶数值
int top()
{
return st[tt];
}
//判空
bool empty()
{
if (tt == 0)
return true;//空
else
return false;//不为空
}
四.单调栈
单调栈(Monotonic Stack)是一种数据结构,用于解决一类特定的问题,通常用于求解数组中元素的下一个更大元素、前一个更大元素等问题。
单调栈的特点是栈内元素保持单调递增或单调递减。具体来说,对于单调递增栈,栈内元素从栈底到栈顶是递增的;对于单调递减栈,栈内元素从栈底到栈顶是递减的。
在使用单调栈解决问题时,通常会遍历数组元素,维护一个单调栈,根据具体问题的要求,不断更新栈内元素,以得到需要的结果。
模板:
int tt = 0;
//伪代码,具体看题目
for (int i = 1; i <= n; i++)
{
//check是处理逻辑,满足条件就出栈
while (tt && check(st[tt], i))
{
tt--;
}
st[++tt] = i;
}
496. 下一个更大元素 I - 力扣(LeetCode)
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
//单调栈--map
vector<int> ans(nums1.size(), -1);
if(nums1.size() == 0) return ans;
stack<int> st;
st.push(0);
//map哈希映射
unordered_map<int,int> umap;
for(int i = 0; i < nums1.size(); i++)
{
umap[nums1[i]] = i;
}
for(int i = 1; i < nums2.size(); i++)
{
if(nums2[i] > nums2[st.top()])
{
while(!st.empty() && nums2[i] > nums2[st.top()])
{
if(umap.count(nums2[st.top()]) > 0)
{
ans[umap[nums2[st.top()]]] = nums2[i];
}
st.pop();
}
st.push(i);
}
else
{
st.push(i);
}
}
return ans;
}
};
五.队列
维护对头和队尾
const int N = 100010;
//队列
int q[N];
//对头,队尾
int hh = 0, tt = -1;
//向队尾插入一个数
void insert(int a)
{
q[++tt] = a;
}
//从队头弹出一个数
void pop()
{
hh++;
}
//返回队头的值
int front()
{
return q[hh];
}
//判空
bool empty()
{
if (hh <= tt)
return false;//不为空
else
return true;
}
六.循环队列
也就多了个循环,队列满时直接改个方向
//hh表示队头,tt表示队尾的后一个位置
const int N = 100010;
int q[N];
int hh = 0, tt = 0;
//向队尾插入一个元素
void insert(int a)
{
q[tt++] = a;
//队满,循环改向
if (tt == N)
tt = 0;
}
//从队头弹出一个元素
void pop()
{
hh++;
if (hh == N)
hh = 0;
}
//返回队头的值
int front()
{
return q[hh];
}
//判空
bool empty()
{
if (hh == tt)
return true;//空
else
return false;//不为空
}
七.单调队列
单调队列(Monotonic Queue)是一种数据结构,通常用于解决一些需要维护一组数据中的最大值或最小值的问题(比如滑动窗口中的最大值和最小值)。单调队列可以支持在队列两端进行插入和删除操作,并且保持队列中的元素是单调递增或单调递减的。
单调队列的主要特点是在插入元素时,会将比当前元素小的元素从队列尾部删除,以保持队列的单调性质。这样可以确保队列头部始终是当前队列中的最大值或最小值。
在实际应用中,单调队列常用于求滑动窗口中的最大值或最小值等问题,可以在O(N)的时间复杂度内解决这类问题。
单调队列的基本操作包括:
1. 在队尾插入元素,并保持队列的单调性;
2. 在队头删除元素;
3. 获取队列的头部元素(最大值或最小值)。
单调队列可以通过双端队列(Deque)来实现,具体实现方式可以根据具体问题的需求选择单调递增队列或单调递减队列。
接下来用数组来模拟实现单调队列:
模板:
const int N = 100010;
//队列
int q[N];
//队头,队尾
int hh = 0, tt = -1;
//伪代码
for (int i = 0; i < n; i++)
{
//队头是否滑出窗口
while (hh <= tt && check_out(q[hh]))
hh++;
//入队列要保持单调
while (hh <= t && check(q[tt], i))
tt--;
q[++tt] = i;
}