时间复杂度
插入删除节点0(1)
查询节点0(n)
代码
#include
#include
#include
using namespace std;
const int N = 100010;
int head, e[N], ne[N], idx;
//head记录头节点下标
//e[N]记录每个节点的val值
//ne[N]记录每个节点的下一个点的下标
//idx记录当前待用节点的下标
void init()
{
head = -1; //初始状态头节点指向-1
idx = 0; //当前待用空间下标为0,表示链表为空
}
//将x插入到头节点后面
void addHead(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 --)
{
string op;
cin >> op;
if (op == "I") //将x插入到下标为k - 1的节点后面
{
int k, x;
cin >> k >> x;
add(k - 1, x);
}
else if (op == "D")
{
int k;
cin >> k;
if (k) remove(k - 1);
else head = ne[head]; //删除头节点
}
else
{
int x;
cin >> x;
addHead(x); //头插法
}
}
for (int i = head; i != -1; i = ne[i]) //遍历单链表
{
cout << e[i] << " ";
}
return 0;
}
代码
#include
using namespace std;
const int N = 100010;
int head, tail, e[N], l[N], r[N], idx;
//初始化
void init()
{
head = 0,tail = 1;
r[0] = 1,l[1] = 0;
idx = 2;
}
//在k后插入k
void insert(int k, int x)
{
e[idx] = x;
l[idx] = k, r[idx] = r[k]; //idx为x的下标
l[r[k]] = idx, r[k] = idx;
idx ++;
}
//移除点k
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main()
{
int m;
cin >> m;
init();
while (m --)
{
string op;
cin >> op;
int k, x;
if (op == "L")
{
cin >> x;
insert(0, x);
}
else if (op == "R")
{
cin >> x;
insert(l[1], x);
}
else if (op == "D")
{
cin >> k;
remove(k + 1);
}
else if(op == "IL")
{
cin >> k >> x;
insert(l[k + 1], x);
}
else
{
cin >> k >> x;
insert(k + 1, x);
}
}
for (int i = r[0]; i != 1; i = r[i]) //从头节点的右侧开始遍历
{
cout << e[i] << " ";
}
return 0;
}
代码
#include
using namespace std;
const int N = 100010;
int stack[N], tt;
int main()
{
int m;
cin >> m;
while (m --)
{
string op;
cin >> op;
int x;
if (op == "push")
{
cin >> x;
stack[ ++ tt] = x;
}
else if (op == "pop") tt --;
else if (op == "query") cout << stack[tt] << endl;
else
{
if (tt > 0) cout << "NO" << endl;
else cout << "YES" << endl;
}
}
return 0;
}
算法
代码
代码
#include
using namespace std;
const int N = 100010;
int q[N], hh, tt = -1;
int main()
{
int m;
cin >> m;
while (m --)
{
string op;
cin >> op;
int x;
if (op == "push")
{
cin >> x;
q[ ++ tt] = x; //将x从队尾插入
}
else if (op == "pop") hh ++; //弹出队头元素
else if (op == "query") cout << q[hh] << endl; //查询队头元素
else
{
if (hh <= tt) cout << "NO" << endl; //队列非空
else cout << "YES" << endl;
}
}
return 0;
}
代码
#include
#include
using namespace std;
const int N = 100010;
stack <int> s;
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i ++)
{
int x;
cin >> x;
if (s.empty()) cout << "-1 "; //如果栈中为空,表示左边没有比x小的数
else
{
while (s.size() && s.top() >= x) s.pop();
if (s.empty())cout << "-1 ";
else cout << s.top() << " ";
}
s.push(x); //x一定会入栈
}
return 0;
}
代码
#include
using namespace std;
const int N = 1000010;
int a[N], q[N]; //a[N]用于存储数据, q[N]是一个单调队列,存储窗口中单调元素的下标
int hh = 0, tt = -1; //队列首尾指针初始化
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for(int i = 0; i < n; i ++)
{
scanf("%d", &a[i]); //读入a[N]原始数据
}
//-------------------------------输出最小值
for(int i = 0; i < n; i ++) //i是遍历a[N]的指针
{
if(i - k + 1 > q[hh]) hh ++; //当窗口元素数大于k时,队首元素应出队
while(hh <= tt && a[q[tt]] >= a[i]) tt --;//当队列不为空且队尾元素比当前要入队的元素大时,队尾元素出队,使得队中元素单调递增
q[ ++ tt] = i;//当前元素入队
if(i >= k - 1) printf("%d ", a[q[hh]]);//当窗口元素具有k个数时,输出队首元素下标对应的值
}
printf("\n");
//-------------------------------输出最大值
hh = 0, tt = -1; //记得同样操作再做一遍时,记得初始化hh和tt
for(int i = 0; i < n; i ++)
{
if(i - k + 1 > q[hh]) hh ++; //当窗口元素数大于k时,队首元素应出队
while(hh <= tt && a[q[tt]] <= a[i]) tt --;//当队列不为空且队尾元素比当前要入队的元素小时,队尾元素出队,使得队中元素单调递减
q[ ++ tt] = i;
if(i >= k - 1) printf("%d ", a[q[hh]]);
}
return 0;
}
#include
using namespace std;
const int N = 1000010;
int n, m;
char p[N], s[N];
int ne[N];
int main()
{
//字符数组可以直接cin整个串,可以从下标1开始输入,string不可以从下标1开始输入
cin >> n >> p + 1 >> m >> s + 1;
//填充ne数组,当i等于1时不匹配,只能回退到0,所以ne[1] = 0, 所以i 从2开始
for (int i = 2, j = 0; i <= n; i ++)
{
while (j && p[i] != p[j + 1]) j = ne[j]; //当不匹配时,j回退到最长相同前缀的末尾
if (p[i] == p[j + 1]) j ++; //如果能够匹配,j向后移动
ne[i] = j; //以i结尾的子串后缀最长能够匹配上子串的前j个字符
}
//遍历整个主串,依次输出匹配成功的起始位置
for (int i = 1, j = 0; i <= m; i ++)
{
while (j && s[i] != p[j + 1]) j = ne[j]; //当子串与主串匹配不成功,子串后退到最长前缀的位置
if (s[i] == p[j + 1]) j ++; //如果能够匹配,j向后移动
if (j == n) //匹配成功,i - n 是起始位置
{
cout << i - n << " ";
j = ne[j]; //将j退回最长前缀的位置,继续后续的匹配
}
}
return 0;
}
代码
#include
using namespace std;
const int N = 100010;
int son[N][26], cnt[N], idx;
//son记录trie数,cnt记录每个词出现的次数,idx记录每个字符所占用的下标
//加入字符串
void add(char str[])
{
//idx = 0既表示根节点也表示空节点
int p = 0;
for (int i = 0; str[i]; i ++) //字符串数字末尾为0
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++ idx; //当前字符不存在,增加
p = son[p][u]; //向下继续遍历
}
cnt[p] ++; //p指向字符串最后一个字符,计数++
}
int query(char str[])
{
int p = 0;
for (int i = 0; str[i]; i ++)
{
int u = str[i] - 'a';
if (!son[p][u]) return 0; //若没找到,直接返回0
p = son[p][u]; //向下继续遍历
}
return cnt[p]; //p指向字符串最后一个字符
}
int main()
{
int n;
cin >> n;
while (n --)
{
char op;
char x[N];
cin >> op >> x;
if (op == 'I') add(x);
else cout << query(x) << endl;
}
return 0;
}
代码
#include
using namespace std;
const int N = 100010, M = 4000000;
int son[M][2], a[N], idx;
void add(int x) //将x的31位二进制插入trie树
{
int p = 0;
for (int i = 30; ~i; i --) //i >= 0,当i等于-1时,二进制全为1,取反则为0
{
int &s = son[p][x >> i & 1]; //准备走的路
if (!s) s = ++ idx; //如果没有路,就自己创一个
p = s; //继续往下走
}
}
int query(int x)
{
int p = 0, res = 0;
for (int i = 30; ~i; i --)
{
int s = x >> i & 1;
if (son[p][!s]) //另一个岔路是否走得通
{
res += 1 << i; //结果
p = son[p][!s];
}
else p = son[p][s]; //走不通就走走同一条路
}
return res;
}
int main()
{
int n;
cin >> n;
for (int i = 0; i < n; i ++) cin >> a[i], add(a[i]);
int res = 0;
for (int i = 0; i < n; i ++) res = max(res, query(a[i]));
cout << res;
return 0;
}
算法
代码
#include
using namespace std;
const int N = 100010;
int n, m;
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x]; //返回父节点p[x],而不是x,根节点处x = p[x],但是递归调用,后面的节点x != p[x]
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++) p[i] = i; //将每一个节点的父节点初始化为自己,相当于每一个节点自成一个集合
while (m --)
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (op[0] == 'M') //op[0]
{
p[find(a)] = find(b); //将a集合纳入b集合,a的父亲节点为b的根节点
//在合并时可以加入cnt数组记录集合中点的数量
//cnt[find(b)] += cnt[find(a)];
}
else
{
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
代码
#include
using namespace std;
const int N = 50010;
int p[N], d[N];
int find(int x)
{
if (p[x] != x)
{
int t = find(p[x]);
d[x] += d[p[x]]; //d[i]存储的是i结点到它父节点的距离,d[x]就是等于x到p[x]的距离
p[x] = t; //先更新距离,再更新父节点
}
return p[x];
}
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i ++) p[i] = i;
int res = 0;
while (k --)
{
int op, a, b;
cin >> op >> a >> b;
if (a > n || b > n) res ++;
else
{
int pa = find(a), pb = find(b);
if (op == 1)
{
//a和b在同一个集合中,但是a的类型和b的类型不相同(根据a和b到根节点的距离判断)
//判断a和b模三是否相等,不要写成a % 3 != b % 3,这样写存在正负号的问题
if (pa == pb && (d[a] - d[b]) % 3) res ++;
else if (pa != pb)
{
p[pa] = pb;
d[pa] = d[b] - d[a]; //使得(d[a] + d[pa] - d[b]) % 3 == 0
}
}
else
{
//a和b在同一个集合中,但是a吃b(根据a和b到根节点的距离判断)
if (pa == pb && (d[a] - d[b] - 1) % 3) res ++;
else if (pa != pb)
{
p[pa] = pb;
d[pa] = d[b] - d[a] + 1; //使得(d[a] + d[pa] - d[b] - 1) % 3 == 0
}
}
}
}
cout << res;
return 0;
}
算法
初始建堆,若采用插入的方法,则时间复杂度为0(nlogn)
优化方法:将所有元素先乱序存入数组(建立起一个乱序完全二叉树),然后对这棵树的前n/2个元素进行一遍down操作,即可构成堆
该优化方法由下图的证明可以得出时间复杂度为0(n)
代码
#include
using namespace std;
const int N = 100010;
int h[N], cnt; //堆的大小不要定义成size,会报错
void down(int u)
{
int t = u; //使用t记录三个值中最小值的编号
if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
int n, m;
cin >> n >> m;
//构建完全二叉树
for (int i = 1; i <= n; i ++) cin >> h[i];
cnt = n;
//0(n)建堆
for (int i = n / 2; i; i --) down(i);
//输出堆顶,并删掉堆顶
while (m --)
{
cout << h[1] << " ";
h[1] = h[cnt];
cnt --;
down(1);
}
return 0;
}
算法
代码
算法
并且取模的这个数需要是质数,并且尽可能离二的整次幂尽可能的远,这样发生冲突的概率是最小的
这两种方法的删除操作都是开一个数组标记,不会真正进行删除
总结如下
代码
********拉链法********
#include
#include
using namespace std;
const int N = 100003;
int h[N], e[N], ne[N], idx;
int n;
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof(h));
while (n --)
{
char op[2];
int x;
scanf("%s%d", op, &x);
int k = (x % N + N) % N; //找到x应该在的位置
if (op[0] == 'I') //将x插入对应的链表,头插法
{
e[idx] = x;
ne[idx] = h[k];
h[k] = idx ++;
}
else //找到对应链表,遍历该链表,查看x是否存在
{
bool flag = false;
for (int i = h[k]; ~i; i = ne[i])
{
if (e[i] == x)
{
flag = true;
break;
}
}
if (flag) puts("Yes");
else puts("No");
}
}
return 0;
}
******开放寻址法******
#include
#include
using namespace std;
const int N = 200003, null = 0x3f3f3f3f; //开放寻址法通常是开到元素个数的两到三倍
int h[N];
int n;
int find(int x)
{
int k = (x % N + N) % N;
while (h[k] != x && h[k] != null) //当前坑位有人并且不是x
{
k ++;
if (k == N) k = 0; //如果已经遍历到最后一个坑位了,就从0号坑从头开始找
}
return k; //如果x存在,则返回x的位置,如果x不存在,则返回x应该在的位置
}
int main()
{
scanf("%d", &n);
memset(h, 0x3f, sizeof(h));
while (n --)
{
char op[2];
int x;
scanf("%s%d", op, &x);
int k = find(x); //找到x应该在的位置
if (op[0] == 'I') h[k] = x;
else //
{
if (h[k] != null) puts("Yes");
else puts("No");
}
}
return 0;
}
代码
#include
using namespace std;
typedef unsigned long long ULL; //没有等号,要加分号,相当于自动取模2^64
const int N = 100010, P = 131; //或者P可以取13331
int n, m;
ULL h[N], p[N];
char str[N];
//计算L~R这段字符串的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1]; //多项式对齐
}
int main()
{
scanf("%d%d%s", &n, &m, str + 1); //str从1开始,所以使用char数组存储
p[0] = 1; //p^0
for (int i = 1; i <= n; i ++)
{
p[i] = p[i - 1] * P; //因为涉及i - 1,所以i从1开始
h[i] = h[i - 1] * P + str[i]; //第一位字符哈希值就是字符本身,往后*P + 字符
//str[i]的值是多少没有关系,只要不为0且不相同就行
}
while (m --)
{
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
if (get(l1, r1) != get(l2, r2)) puts("No");
else puts("Yes");
}
return 0;
}