1、时间复杂度和空间复杂度
简单理解:
输入一个数组,数组长度n,
时间复杂度:for循环1次对应时间复杂度O(n^1)
空间复杂度:内存存了当前输入的数组对应空间复杂度O(n^0),产生一个新的数组对应空间复杂度O(n ^ 1)
遇到一道二叉树的题目,通用思考过程
1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值。
3、无论使用哪一种思维模式,你都要明白二叉树的每一个节点需要做什么,需要在什么时候(前中后序)做。
(那 层序遍历/BFS 算其中哪种?)
二叉树题型主要是用来培养递归思维的,而层序遍历属于迭代遍历
何时考虑使用后序遍历?
- 一般情况用前序即可。
- 一旦你发现题目和子树有关,那大概率要给函数设置合理的定义和返回值,在后序位置写代码了。
- 利用后序位置的题目,一般都使用「分解问题」的思路。因为当前节点接收并利用了子树返回的信息,这就意味着你把原问题分解成了当前节点 + 左右子树的子问题。
二叉树在其他题型中的抽象
- 动态规划关注整棵「子树」
- 回溯算法关注节点间的「树枝」
- 回溯算法必须把「做选择」和「撤销选择」的逻辑放在 for 循环里面,否则怎么拿到「树枝」的两个端点?
- DFS 算法关注单个「节点」
数组类题目
3*3类:
子集&数组(限定个数的子集)(无序)、排列(有序)
*
元素无重复选择不重复、元素重复选择不重复、选择重复
滑动窗口:
1、什么时候应该移动 right 扩大窗口?窗口加入字符时,应该更新哪些数据?
2、什么时候窗口应该暂停扩大,开始移动 left 缩小窗口?从窗口移出字符时,应该更新哪些数据?
贪心
时刻记住,
算法的本质是穷举,遇到任何题目都要先想暴力穷举思路,
穷举的过程中如果存在冗余计算,就用备忘录优化掉。
如果提交结果还是超时,那就说明不需要穷举所有的解空间就能求出最优解,这种情况下肯定需要用到贪心算法。
可以仔细观察题目,是否可以提前排除掉一些不合理的选择,是否可以直接通过局部最优解推导全局最优解。
应试技巧
时间复杂度
比如一个题目给你输入一个数组,其长度能够达到 10^6 这个量级,那么我们肯定可以知道这道题的时间复杂度大概要小于
O(N2),得优化成 O(NlogN) 或者O(N) 才行。因为如果你写的算法是 O(N 2 ) 的,最大的复杂度会达到 10^12 这个量级,在大部分判题系统上都是跑不过去的。
为了把复杂度控制在 O(NlogN) 或者O(N),我们的选择范围就缩小了,可能符合条件的做法是:对数组进行排序处理、前缀和、双指针、一维 dp 等等,从这些思路切入就比较靠谱。像嵌套 for 循环、二维 dp、回溯算法这些思路,基本可以直接排除掉了。
再举个更直接的例子,如果你发现题目给的数据规模很小,比如数组长度 N 不超过 20 这样的,那么我们可以断定这道题大概率要用暴力穷举算法。
因为判题平台肯定是尽可能扩大数据规模难为你,它一反常态给这么小的数据规模,肯定是因为最优解就是指数/阶乘级别的复杂度。你放心用 回溯算法 招呼它就行了,不用想别的算法了。
框架思维
题让我们操作字符串。字符串本质就是个数组,所以这题考察的大概是数组相关的算法技巧。
数组有什么算法技巧?你心里有数,无非就是
二分查找、
快慢指针、
左右指针、
滑动窗口、
前缀和数组、
差分数组,主要就这些。
算法题的技巧就是,把大的问题细化到一个点,先研究在这个小的点上如何解决问题,然后再通过递归/迭代的方式扩展到整个问题。
DFS
9=3*3种框架:
3种输入的数组类型:
- 元素无重复、不能重复选择
- 元素无重复、可以重复选择
- 元素有重复,不能重复选择(相当于用元素个数限定了重复选择的次数)
3种排列类型:
- 排列(长度确定、有序)
- 组合(长度确定、无序)
- 子集(长度不定的组合)
岛屿问题
关键在于:
用DFS淹掉哪里。
普通的岛屿:遇到陆地,把相连的淹掉
非边缘岛屿:遇到边缘,把相连的淹掉
子岛屿:遇到不重合的地方,把相连的淹掉,说明这个岛屿没用
DP
一旦涉及到子序列和最值,那几乎可以肯定,考察的是动态规划技巧,时间复杂度一般都是 O(n^2)
两个字符串求子序列
都是用两个指针 i 和 j 分别在两个字符串上移动,大概率是动态规划思路。
python
float(‘-inf’)
dp = [0] * len(nums)
dp = [[0] * m for _ in n]
ord(text[i])
来源
https://labuladong.online/