算法白话总结
参考: https://programmercarl.com/
推荐参考本博客总结的 algo_newbie , 和本文对照着看
概绍
本群的每日刷题打卡活动, 按照 GitHub 49k star的项目 https://github.com/youngyangyang04/leetcode-master 的刷题顺序.
跟着群里有个伴一起刷题或许更容易坚持达成每日一题的目标. 做完题目之后可以在群里的小程序”今日leetcode刷题打卡”里打卡.
- 网页版: 代码随想录 https://programmercarl.com/
- 本博客只记录那些有明显自我疑问而<<代码随想录>>没有说明清楚的题目, 会标识出来并注释
本文完整参考代码
https://github.com/no5ix/no5ix.github.io/blob/source/source/code/test_algo_na.java
数组
lc704 - 二分查找 - 20240814
1 | class Solution { |
lc977 - 有序数组的平方 - 20240916
- https://programmercarl.com/0977.有序数组的平方.html#算法公开课
- https://leetcode.com/problems/squares-of-a-sorted-array/description/
1 | class Solution { // lc977 |
字符串
lc28 - 实现strStr() - 20240923 - KMP
- https://programmercarl.com/0028.实现strStr.html#算法公开课
- https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/
看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如上动画所示:
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。
所以要看前一位的 前缀表的数值。
前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
最后就在文本串中找到了和模式串匹配的子串了。
1 | class Solution { |
二叉树
二叉树递归解法的写法窍门
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii, https://programmercarl.com/0112.路径总和.html#相关题目推荐)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先, https://programmercarl.com/0236.二叉树的最近公共祖先.html#算法公开课)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(这种情况符合: https://programmercarl.com/0112.路径总和.html#算法公开课)
前序
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
1 | class Solution { |
中序
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
1 | class Solution { |
后序
- 先序遍历是
中左右
- 调整代码左右循序
- 变成
中右左
-> 反转result数组 ->左右中
- 后序遍历是
左右中
层序
1 | class Solution { |
高度
- 二叉树节点的高度:指从
该节点
到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始) - 二叉树节点的深度:指从
根节点
到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始) - 而根节点的高度就是二叉树的最大深度
深度
- 求深度用
层序遍历
是最适合的最直观容易理解 - 二叉树的深度: 根节点到最远叶子节点的最长路径上的节点数。
- 叶子节点: 是指没有子节点的节点。
最大深度
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,
1 | class Solution { |
最小深度
最小深度: 是从根节点到最近叶子节点的最短路径上的节点数量。
1 | class Solution { |
回溯
模板
1 | void backtracking(参数) { |
组合
没有剪枝的版本
1 | class Solution { |
剪枝的版本
图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。
所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
注意代码中i,就是for循环里选择的起始位置。
for (int i = startIndex; i <= n; i++) {
接下来看一下优化过程如下:
- 已经选择的元素个数:
path.size();
- 还需要的元素个数为:
k - path.size();
- 在集合n中i最大可以从该起始位置开始遍历 :
n - (k - path.size()) + 1
(备注:n - (k - path.size())
就是表示从已经最大的数n往回退几个数再开始搜索遍历, 退几个数呢? 退k - path.size()
个数, 后面多出来的那个+1
是因为要包括起始位置,我们要是一个左闭的集合)
那为什么 n - (k - path.size()) + 1
有个+1呢? 因为包括起始位置,我们要是一个左闭的集合。
举个例子,n = 4,k = 3
, 目前已经选取的元素为0个(即path.size()为0),n - (k - 0) + 1
即 4 - ( 3 - 0) + 1 = 2
。
从2开始搜索都是合理的,可以是组合[2, 3, 4]
。从”3”开始就不合理了, 因为只能[3, 4, ?]
, “4”后面没有了, 只有2个数字”3”和”4”能用.
这里大家想不懂的话,建议也举一个例子,就知道是不是要+1了。
所以优化之后的for循环是:
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置
优化后整体代码 diff 如下:
1 | class Solution { |