前言:本篇用了我比较长的时间,干货较多,甚至一篇顶三篇,希望各位耐心观看。
Part 1:单调栈
单调栈是一种数据结构,一般分为单调递增栈和单调递减栈。单调栈一般用来解决寻找下一个大于或小于m的值。
我们把得到的答案放到K数组里。
一个经典例题:定义函数
在单调栈中,如果栈为空,那么K[i]就为0,如果栈不为空,则判断栈顶是否大于a[i],如果不是,那么弹出栈顶。最后,不管什么情况,都要把a[i]入栈。需要注意的是,由于中间的中转是栈,所以i从n开始,到1结束。
单调栈核心代码:
while(!s.empty()&&a[s.top()]<=a[i])s.pop(); 0:s.top(); s.push(i); 仅此三行。
模板题
Part 2:单调队列
同样,队列内的元素是单调递增或单调递减的,滑动窗口是一道很好的模板题。
单调队列的核心有两个部分,三个步骤。
第一个部分(第一步)是队头出队,不管什么情况,队头都要出队,及head++
第二部分,第二步是判断,如果新元素小于队尾元素,那就直接tail++,也就是直接入队,因为它有可能成为最大值;如果新元素大于等于队尾元素,那么先tail--删除队尾元素,再tail++加入新元素,因为队尾元素一定不可能成为新的最大值。
第三步就是按照第二步判断出来的执行。
核心代码同样只有3行:
if(h<=t&&q[h]+m<=i) h++;
while(h<=t&&a[i]>=a[q[t]]) t--;
q[++t]=i;
当然,有的题非让你用单调递减队列,相反即可。
Part 3 并查集:
并查集算是比较高级的数据结构了,它主要用来合并序列和查询序列,查询这一方面,其他的算法层出不穷,但合并这一方面,并查集的优势十分显著。
先说一下基本的定理,并查集是通过找寻/更改“祖先”来进行
合并和查询的,所谓“祖先”其实就是这个数字存在于哪一个集合,一开始要进行初始化,每一个数都以自己的节点号命名一个有1个点的集合,如1号点在1集合1,114514号点就在集合114514。
合并操作只需要更改公共的祖先即可,比如,一开始,
1号点在1集合1,114514号点在集合114514,如果想把集合114514和集合1合并,那么只需要把114514号点的祖先改为1即可。对于多个点的集合,只要更改其中一个集合中的祖先即可,这里的祖先,指的是点编号与集合编号相同的点,因为它是初始状态,所以是“祖先”。
那么既然
祖先指的是点编号与集合编号相同的点,那么查找操作就是找某个点的祖先,看他是不是原始祖先(即
编号与集合编号相同的点
),如果不是,再搜这个点的祖先的祖先,直到找到原始祖先为止。
可以用递归来写:
int find(int k) {
if(f[k]==k) return k;
return f[k]=find(f[k]);
}