经典算法解题思路汇总
*1.在线算法求数组的中位数
维护一个大顶堆,一个小顶堆
priority_queue<int, vector
priority_queue<int, vector
为方便计算中位数,需保持大顶堆大小不小于小顶堆,所以要先判断堆大小
如果大顶堆大小等于小顶堆,当插入新元素时,如果该元素比大顶堆最大元素小或相等,则直接插入
如果比大顶堆最大元素大则先将元素插入到小顶堆,再从小顶堆拿一个元素过来,使大顶堆个数还是小于小顶堆
如果大顶堆元素不等于小顶堆,如果插入元素大于大顶堆,则直接插入小顶堆,如果小于大顶堆,则把大顶堆堆顶元素
插入大小顶堆,再把插入元素放到大顶堆中.
*2.字符串替换技巧
通常有索引下标、源串、目的串
通常的做法是对索引下标排好序,依次替换,从前到后替换时,需要拷贝一个新串出来,按顺序遍历源串,拼接新串即可
如果希望在源串上操作,则好的做法是,下标排好序后,从后向前替换,这样之前的索引位置还是有效的.
有很多问题是从后向前解决时更方便
*3.字符串处理技巧
使用trie树、使用suffix trie树,注意实现search和insert函数
*4.图
注意根据边可以得到图,对于无向图,因为边是双向关系,在遍历无向图时,需要任选一个点,作为根节点,并使其父节点为-1
然后跟他相邻的节点,作为其子节点,因为存在父子关系,当递归遍历子节点时,每个节点都需要维护与改节点相邻的节点,
遍历时注意排除掉 节点等于 父节点的情况.
*5 BFS和DFS
两者算法复杂度都是o(n), 但是BFS需要使用额外空间,dfs不需要
BFS需要注意先设置标志位和后设置标志位的区别,通常先设置标志位后,可以防止重复加入元素,所以通常的做法都是先标记元素,后处理逻辑
DFS同样需要考虑先标记和后标记的问题,通常也是先标记,dfs走到改点时,直接标记改点已经走过,如果需要回溯的,返回时需更新会原先状态,不需要回溯的则不用处理.
*6. 二分查找用于逼近极值点
常规的二分查找用于找值,二分查找还可以用于分割问题,例如一个函数由两个子函数组成,一个子函数a单调递增,一个子函数b单调递减, 如果说我们想找到两个子函数值最接近的点,可以利用二分,在坐标点i,如果a(i)的值小于b(i)时,则在i的右半部查找,如果a(i)大于b(i), 则去i左半部查找,所以这里判断查找方向是依据左右两部分的值
尽量往两者值接近的地方去. 画一个递增的曲线和递减的曲线,我们找的就是两者的交点. 该点,a和b的取值最接近
*7. 找最少步数
a) 考虑BFS,遇到符合条件的即可以退出,如果使用DFS则需要遍历全部的可能,没有BFS快
b) djstra算法,单源最短路径,知道o(ElogE)解法即可,根据题目要求看是否可以使用
c) 动态规划,最值问题,也可以通过DP的递推公式来解决
*8.动态规划常见的思路
- 一维dp 以当前元素结尾, 是否可以建立递推关系式
- 二维dp, 以数组1中的i结尾, 以数组2中的j结尾, 建立递推关系式
*9.空间复杂度优化思路
- 如果递推式遍历数组时, 遵循从左到右, 从上到下, 则二维数组可以优化为一维
例如: dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 该递推式表明求dp[i][j]时, 需要先求出上方和左侧元素的值, 满足优化的条件, 所以可以优化为dp[i] += dp[i - 1] when i >= 1
*10. Map是一个非常好的数据结构, 它本身具有扩展性, 如果key可以充当唯一id的话, 它是可以用来替换hashSet来用于 去重复或者标记有没有访问过某个节点
比如在 图的复制题目中,就可以直接使用hashmap来保存已访问过的节点的值.(mem dp + 去重复set)
*11. 背包问题分为 01背包, 完全背包和多重背包, 这类问题的关键是看到题目后, 能想到问题可以转化为背包
01背包的特点是, 物品只能取0或者1个,并且背包有容量限制, dp[i][j] 第一维是物品个数, 第二维是容量 (比如, 金币总数, 重量总数等等), dp[i][j]的值表示最大价值或者题目要求输出的东西
完全背包的特点是, 物品没有选取的上限, 但是有容量限制
多重背包的特点是, 物品有选取上限, 但是可以选多个, 并且有容量限制
总之, 背包问题有固定的模板, 通常都是dp[i][j] i 表示前i个物品, j表示容量, dp[i][j]表示, 当考虑前i个物品, 容量上限为j时, 所能得到的最优解
通常对于第i个元素自身, 我们有两种选择, 考虑第i个元素和不考虑它, 基于此dp[i][j] 与 dp[i - 1[j] dp[i - 1][j - i的容量] 相关, 由此我们可以得出递推式
同时要注意的一点是, 如何正确的初始化dp数组