一.链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
这样说有些抽象,找了个图方便理解
每个小块都被分成两块,分别是此位置所包含的值,和地址,也被称为结点,是结构体。
注意:
- 从图中可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续。
- 现实中的结点一般都是从堆上申请出来的。
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
链表的种类有很多种,例如:
1.单向或双向
2.带头或不带头
3.循环或非循环
常用的两种
单链表的实现和题目以及代码:
#include <iostream>
using namespace std;
const int N = 100010;
// head 表示头结点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 将x插到头结点
void add_to_head(int x)
{
e[idx] = x, ne[idx] = head, head = idx ++ ;
}
// 将x插到下标是k的点后面
void add(int k, int x)
{
e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
}
// 将下标是k的点后面的点删掉
void remove(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
int m;
cin >> m;
init();
while (m -- )
{
int k, x;
char op;
cin >> op;
if (op == 'H')
{
cin >> x;
add_to_head(x);
}
else if (op == 'D')
{
cin >> k;
if (!k) head = ne[head];
else remove(k - 1);
}
else
{
cin >> k >> x;
add(k - 1, x);
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
cout << endl;
return 0;
}
二.单调栈
单调递增栈:在保持栈内元素单调递增的前提下(如果栈顶元素大于要入栈的元素,将将其弹出),将新元素入栈。
单调递减栈:在保持栈内元素单调递减的前提下(如果栈顶元素小于要入栈的元素,则将其弹出),将新元素入栈
时间复杂度是O(n)
什么时候使用单调栈呢
给定一个序列,求序列中的每一个数左边或右边第一个比他大或比他小的数在什么地方;
#include <iostream>
using namespace std;
const int N = 100010;
int stk[N], tt;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (tt && stk[tt] >= x) tt -- ;//如果栈顶元素大于当前待入栈元素,则出栈
if (!tt) printf("-1 ");//如果栈空,则没有比该元素小的值。
else printf("%d ", stk[tt]);//栈顶元素就是左侧第一个比它小的元素。
stk[ ++ tt] = x;
}
return 0;
}
三.单调队列
单调队列的典型例题,滑动窗口
#include <iostream>
using namespace std;
const int N = 1000100;
//单调队列一般用双端队列保证其单调性
int a[N], q[N], n, k;
//队头和队尾,在队尾插入,队头获取
int front = 0, tail = -1;
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
//先找每个窗口的最小值
for (int i = 0; i < n; i++) {
//如果当前队头在数组的下标小于当前窗口的最小下标,这个窗口就不包含这个元素了那么无论如何都要剔除队头这个元素
//所以要在队头删除这个元素
if (front <= tail && i - k + 1 > q[front]) front++;
//保证单调性,在队尾删除(为什么要在队尾删除,简单来说在队头删除不能保证单调
//比如-3 5为当前队列,当前的元素为3,如果在队头操作,那么按照a[i] <= a[q[front],有3 > -3,因此不做删除操作
//但是接下来就出现问题了,3就要入队了。此时队列就是-3 5 3,不符合单调性了!
//但如果在队尾操作,按照a[i] <= a[q[tail],有3 < 5,就要让5出队
//之后3入队,队列就是-3 3,满足单调性
while (front <= tail && a[i] <= a[q[tail]]) tail--;
q[++tail] = i;
//队头为窗口的最小值
if (i >= k - 1) printf("%d ", a[q[front]]);
}
printf("\n");
//这次找最大值,同理
front = 0, tail = -1;
for (int i = 0; i < n; i++) {
if (front <= tail && i - k + 1 > q[front]) front++;
while (front <= tail && a[i] >= a[q[tail]]) tail--;
q[++tail] = i;
if (i >= k - 1) printf("%d ", a[q[front]]);
}
}
教学可以看b站视频单调队列滑动窗口,董晓算法,讲的非常详细