数组模拟链表
注意初始化时使用了数组的几个元素,让第k个数与数组下标对应,需要 k 加减 x。
单链表
在k后插入值插入
ne[idx] = ne[k];
ne[k] = idx;
双链表
在k的右节点插入一个数,若想在左边插入 add( l[k] , x )
r[idx] = r[k];
l[idx] = k;
l[r[k]] = idx;
r[k] = idx;
删除k节点。
r[l[k]] = r[k];
l[r[k]] = l[k];
模拟栈
栈:先进先出 队列:先进后出
static int N = 100010, tt;
static int stk[] = new int[N];
static void push(int x){
stk[++tt] = x; //进栈
}
static void pop(){
tt --; //出栈
}
static String empty(){
return tt > 0 ? "NO" : "YES"; //判断是否为空并返回
}
static int query(){
return stk[tt]; //返回栈顶元素
}
双栈运算
1+2+3*4*5,
运算符栈(op_stack)和数字栈(num_stack),下面图解:从左到右
模拟队列
看个人习惯,我初始化队列的两指针为0,队尾插入 q[end ++] = x,队头弹出 q[ head ++],
判空 head == end ? "YES" : "NO",查询队头对尾 return q[head],return q[end]
单调栈
当某个点在左边且他的值大于右边的点,就将他删除,从而形成单调栈。
//循环判断若栈顶点大于新注入点,将栈顶点移除栈,相同也要移除只留一个保证单调性
while(!numStack.empty() && numStack.peek() >= number) numStack.pop();
单调队列 - 滑动窗口
左边是原数组,红框是画图窗口、右边是队列。
求滑动窗口中的最小值:当窗口中左边的数大于右边,那么她永远不可能作为最小值输出,直接从队列里移除(先进先出)。
而后进来的数比队尾的数小,则剔除队尾继续比较,直到该队列为”单调递增“位置。将每次滑动窗口最小的数放在队头,每次取最小值时取对头就行。最大值同理。
for(int i = 0; i < n; i ++){
a[i] = Integer.parseInt(str[i]);
//判断当队列不为空、窗口已经形成并移动下一格时出队上一格最小值
if(head != end && i >= k && a[i - k] == q[head]) head ++;
while(head != end && q[end - 1] > a[i]){ end --; q[end] = 0;}
q[end++] = a[i];
//判断已形成了窗口输出最小值,刚开始没有形成窗口
if(i + 1>= k) log.write(q[head]+" ");
}
KMP
字符串中最长的后缀和最长的前缀相等,称为next[],
下面分别是构建 next[ ] ,和kmp匹配过程
int next[] = new int[n + 1];
//构建next数组,next[1]肯定为0,因为只有一个字符
for(int i = 2, j = 0; i < n + 1; i ++){
while(j != 0 && cp[i] != cp[j + 1]) j = next[j]; //前后缀匹配失败,指针回退到next[j]
if(cp[i] == cp[j + 1]) j ++; //前后缀匹配后长度改变
next[i] = j; //写入模板串该点的next数组(即这么长的字符串前后缀匹配最大的长度)
}
for(int i = 1, j = 0; i < m + 1; i ++){
while(j != 0 && cs[i] != cp[j + 1]) j = next[j];
if(cs[i] == cp[j + 1]) j ++; //计数并进行下一个字符匹配
if(j == n){ //完全匹配
log.write(i - n +" ");
j = next[j]; //继续搜索
}
}
Trie树
高效存储和查找字符串。
求字符串存在的数量 AcWing 字符串统计
已存在字符串标记 *,方便查找。
//形成结构有点像链表,son[p][num] 相当于ne[]指针。idx 代表指针使用的下标
static void insert(String s){
int p = 0; //从根节点开始遍历
for(int i = 0; i < s.length(); i ++){
int num = s.charAt(i) - 'a';
if(son[p][num] == 0) son[p][num] = ++idx;
p = son[p][num];
}
count[p] ++; //结束循环后 p指向字符串末尾,标记数量
}
求异或最大
合并集合
可能存在以下关系:
基本原理:每个集合通过一棵树来表示,树根编号就是整个集合的编号,每个节点存放它的父节点,p[x] 表示x集合的父节点。
判断树根:if(p[x] == x)
求x集合的集合编号while(p[x] != x)x = p[x]
合并两个集合:px是x的集合编号,py是y得集合编号,x = y,将两个树根联系在一起
优化:压缩路径
static int find(int x){ //返回p[x]得祖宗节点 + 路径压缩
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}