算法题型与解法总结
还是要思路清晰,能够分析出问题情景,及与其匹配的算法。现阶段很多题都是看了题解思路就明白,但是自己就是想不明白。
集合 + dp
子集状压dp
- 通过集合状态的转变对场景进行处理(由001 -> 010)
- 可以考虑转换思考维度, 从横向转成竖向再进行(集合的遍历) (==LCP 53.守护太空城==)
- 集合转变的递推式(可以是每次+1, 也可以根据场景进行灵活改动 比如由 [1100 -> 1110, 增加了最后一个1])
集合
- (m-1) & j 是求(j 在m 里面的补集)
- a&(-b) 或 (a^b) &a , 是求a-b的差集
- 遍历所有子集(会有重复子集) pre=mask; while true:pre=(pre-1)&mask
树
- 判断子树相同:
- 对子树进行序列化,类似于(a,(null),(null))
- 给每个不同的子树进行编号, 使用三原则(node.val, idx(node.left), idx(node.right) ) 可以唯一确认一个子树
- 求树的直径
连通图
- dfs时间戳: 可以用来判断是否某结点的子孙结点(==2232题==)
- (无环连通图)删除边的枚举方式: 如果以某一点为root结点开始遍历,则使用遍历结点的方式遍历(此结点与其父结点的边)即可
- 无向图,进行遍历的时候,记录父结点,避免进行循环遍历
dp
-
注意点
- 状态转移公式
- 初始值
-
场景分析
- 划分型dp(==2369题==)
- 子序列 + 相邻 dp(==2370题==)
- 数位dp(==2376题==)
深搜广搜
贪心
- 要点
- 有序
- 确认排序后,贪心的条件是否满足
- 使用场景
- 分组贪心模拟(==2375题==)
行列式
- 可先对(行/列聚合)后再进行处理
前缀和
前缀和的前缀和
- 思路转化 (==2281题==)
- sum(min(子序列) * sum(子序列)) = v * (所有包含V的子序列的和) = v * [sum(L,R 内所有子序列)] 【注意L和R的边界】
- [sum(L,R 内所有子序列)] 可以使用前缀和的前缀和计算
子序列(非连续的子串)
-
思路:
-
所有子序列, 多重条件(a, b )下的和
- 找对一个相对固定条件,转化子序列的处理方式(比如:分别计算每个字符的贡献(==828题==) )
-
字符
- 第一思路: 使用长度为26的列表
- 可以使用字母枚举的情况,来提升执行
位运算
- 优势: 可有效缩短存储占用空间
- xor 的性质 a xor b xor a = b
单调栈
- 一般使用场景: 有比较有删除的场景
数据结构
- 使用堆的场景: 插入和删除操作比较多的场景,又可以保持顺序
- 求第n大/小的场景(==2386题==)
- 双堆模拟(==2402题==)
- 双向链表、双指针:无需以下标访问,更新目前指针指向比较频繁的场景
- 哈希表
- 二分搜索(适用场景很多)
组合
-
主要看组合的思路是否清晰,场景是否分析全面可靠,
- 分析场景和解决方式很关键( ==参考2306题== 有i无j 和 有j无i 的组进行互换)
-
组合数的递推公式
c(m,n) = c(m-1,n-1) + c(m, n-1)
-
使用组合的时候,注意小球是相同的,还是不同的
- 隔板法: 在n-1个隔板中放置k个相同的小球,总情况数c(n-1, n-1+k) = c(n, n-1+k)
- 如果有a种小球,每种小球有k个,则对原来的结果,使用乘法原理。 f(1) * * a(2) * * * * a(n)
其他
-
逆向思考、增量排序、记忆化搜索
-
求两个数的公因子
private int gcd(int g, int num) { if (g == 0) return num; return gcd(num %g, g); }