第一章 数据结构
三、二叉树
4. 序列化
[297] 二叉树的序列化与反序列化
DFS:
中序的反序列化不能用,因为不能确定root是左子树和右子树中间的哪一位。前序和后序可以写,后序是反过来写,因为能发现,根节点在最后面,反过来的话,就是根、右、左。
BFS:
层级遍历用到queue,因为queue可以前出后进,序列化和反序列化都用到queue,一个逻辑,取出父节点的同时放入两子节点。
未完
5. 后序
[652] 寻找重复子树
后序遍历能复原子树模样,序列化成字符串好比较不同子树,使用一个map放所有子树字符串,一个vector放符合答案的结点。
6. 归并排序
[912] 排序数组
归并排序:两个链表里各放一个指针,比较,谁小拿出来排队,然后指针后移。
一句话总结了归并排序:先把左半边数组排好序,再把右半边数组排好序,然后把两半数组合并。
[315] 计算右侧小于当前元素的个数
和[912]题类似,唯一区别在于vector tmp使用的是vector<pair<int,int>>类型,second位置放的是每个数的初始位置,为了写答案的时候定位。使用一个vector ans传回答案。
7. 搜索树BST(特性篇)
BST的中序遍历结果是升序的。
[230] 二叉搜索树中第K小的元素
解法并不是最高效的解法,而是仅仅适用于这道题。
[538] 把二叉搜索树转换为累加树
二叉树的通用思路:思考每个节点干什么。
BST问题的思考方向:1. 利用BST左小右大的特性。2. 利用中序遍历即升序的特性。
8. 搜索树BST(基操篇)
[98] 验证二叉搜索树
如果是正常的思路,每次验证只能管自己的左右子树,管不了孙子树,所以参数设置max,min传下去,就可以约束。
[700] 二叉搜索树中的搜索
ok
[701] 二叉搜索树中的插入操作
不要用return,return还要判断是左还是右,这种直接返回在=号后侧可以直接赋值。
[450] 删除二叉搜索树中的节点
删除的三种情况,最后一种:有左子树和右子树,需要找到右子树最小的结点,value赋值过去,然后删除这个最小结点即可(左子树同理,移动最大的结点)。
//二叉搜索树的模板
void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);//root.right=BST(root.right, target);这种也可以
if (root.val > target)
BST(root.left, target);
}
9. 搜索树BST(构造篇)
[96] 不同的二叉搜索树
有更简单的方法。。此方法也是老方法,除了增加一二维vector记录,防止重复计算。
[95] 不同的二叉搜索树 II
与题【96】不同之处在于如何保存每次配好的子树,反正我想了很久没想出来。
10. 快速排序的正确理解方式及运用
一句话快速排序:是先将一个元素排好序,然后再将剩下的元素排好序;是一个构造二叉搜索树的过程。
但是二叉搜索树有不平衡的风险,最极端是编程一个链表,都排到一边去了。
- 使用洗牌算法
- 在
partition
函数中随机选择数组元素作为分界点
[912] 排序数组
总共分三步,1、选择打乱方式,此处推荐两种(1.每次都随机选(这个方法) 2.洗牌算法随机打乱都可以)。2、选择low位置数值为大小分隔数。3、i在前,j在后,相对移动,移动到两者大小都不符合的时候交换。4、在分割好的两区重新上述方法。
二叉树的视角,类似于前序遍历,最后排出来一个BST。
[215] 数组中的第K个最大元素
在【912】的基础上,用if else左右筛选,再return即可。
还有二叉堆的方法,到时候再写。
11. 题目不让我做什么,我就偏要去做什么
[341] 扁平化嵌套列表迭代器
有趣的一道题,有时间不看题解自己写。有两个方法:
在递归方法中,我们在遍历时如果遇到一个嵌套的 子list,就立即处理该 子list,直到全部展开;
在迭代方法中,我们不需要全部展开,只需要把 当前list 的所有元素放入 list 中。
题中NestedInteger是多叉树的结构,考察的显然是迭代,所以考虑在next
还是hasnext
函数中展开时,不太懂,参考负雪明烛。
注意stack特性,逆序放进去。
12. Git原理之最近公共祖先
[236] 二叉树的最近公共祖先
几种情况,一种是q,p一左一右,一种是q就是俩人的公共祖先。框架是前序加后序这样。
[1644] 二叉树的最近公共祖先 II
使用两个bool来记录判断是否量元素都存在,而不是用nullptr来判断利用后序的原理,先判断两条子树是否存在元素,然后再判断root本身是否=目标,这样子能做到不遗漏,如果先判断root,正确就返回了,两条子树还没有查,产生了漏洞。
[235] 二叉搜索树的最近公共祖先
前面好像有几个题解标了BST,但是其实不是的,我看看。这道题很巧,只要root在两个元素之间,就是最近祖先了。
1650. 二叉树的最近公共祖先 III
单链表的内容,没写
13. 完全二叉树的节点数,你真的会算吗?
[222] 完全二叉树的节点个数
结合了普通二叉树和满二叉树,不是复杂的结合,而是判断是不是满二叉树,不是的话用普通二叉树的法子解决(每一层如此)。
四、图
1. 图论算法基础
[797] 所有可能的路径
没啥好说的,可以重新写一下看看,离开的时候要pop
2. 环检测算法及拓扑排序
。。。
五、数据结构
3. 前缀树算法模板秒杀 5 道算法题
什么叫做query本身是键的情况?
关于回溯算法框架和标准多叉树框架的区别,关键在于遍历「节点」和遍历「树枝」的区别。由于 Trie 树将字符存储在「树枝」上,traverse函数是在遍历树枝上的字符,所以采用的是回溯算法框架。
回溯算法类似于深度遍历DFS,节点有值的时候,就增加结果,但是也不能放弃节点孩子的可能性,直到节点为null,然后返回上一层,再进行深入遍历。
api种,有几个和前缀相关的,有几个不和前缀相关的。
感觉hasKeyWithPattern比keysWithPattern复杂多了,因为has的话只要找到就返回,返回路径感觉复杂一些,而收集keys就简单一些。
[1804] 实现 Trie (前缀树) II-remove函数-countWordsStartingWith函数-重写
这道题不容易啊,如果是直接用labuladong的模板,就会产生冗杂,速度慢的,虽然道理是很正的,但是代码量太多了,找时间重写一遍,参考其他人答案。每个函数思路已在题中写出。
[211] 添加与搜索单词
用了两个api,一个经典insert,还有一个是通配符,稍微复杂一点
[648] 单词替换-={nullptr}-
用了trie的两个api,insert完全照搬,prefix类似查找最短prefix。可以直接在private里初始化
[208] 实现 Trie (前缀树)-this-指针数组
三个功能都很类似,主要是设置成员变量,一个指针数组和一个布尔变量,有时间复现一下labuladong的思路。
[677] 键值映射-类似1804-看1804即可
4. 啊这,一道找中位数的算法题把东哥整不会了…
[295] 数据流的中位数
数组,查找简单,插入难,搬移数据要O(N);
链表,查找难也是ON,插入容易;
平衡二叉树介于两者之间,增删改查都是logn,但是放不了重复元素;
优先级队列,有一个排序,但是只能在堆顶查找删除,在队伍中间拿不了元素。
很巧妙的一道题,比上一章节前缀树的所有api操作要好理解,将排序切割成两个优先队列,中位数在队列之间找,或者计算出来。
5. 单调栈解题模板及运用
单调栈用途不广泛,只适用于下一个更大元素这种类型
[496] 下一个更大元素 I-unordered_map-stack-hashmap[num]
找到下一个更大元素,使用单调栈(大概是唯一适配),保存元素,没有重复元素,所以应该联想到哈希表
739 题「每日温度」:
这一道题我以前写过
6. 单调队列通用模板及运用
题目写过,暂时不看这章
7. 图文详解二叉堆,实现优先级队列
二叉堆是特殊的二叉树,是通过数组构成的,分为上浮和下沉两个操作。
delmax是把头尾内容交换,删除尾节点,然后头结点下沉。insert是在尾节点插入,然后上浮。
8. 数据结构设计:用栈实现队列/用队列实现栈
队列和栈的底层都是链表和数组构成。所以两者可以互相表示,只是限于api的不同。
用两个栈实现队列,头对头,正常push,若要pop,导入到另一个栈中,再pop。
队列实现栈(无亮点):push简单,pop的话,队头的
9. 设计 Twitter:合并 k 个有序链表和面向对象设计
还没有看
第二章 动态规划
一、动态规划基本技巧
1. 动态规划解题套路框架
最重要:写出动态转移方程
斐波那契是自顶向下,动态规划是自底向上,一般脱离递归,通过迭代实现
凑零钱问题(最优子结构):子问题之间必须独立
记住重复子问题有两种方法:1、dp数组(自底向上,开头的0位可能是base case) 2、备忘录(可能就是递归,自顶向下)
思路是:
2. 最长递增子序列教你推导状态转移方程
[300] 最长递增子序列-dp数组-O(N^2)
两个for循环,dp数组里放得是,以i位数结尾的最长子序列的长度,所以,先是和前面所有位比较是否能成为末尾,其次在当末尾里,哪一队最长,记下长度,即可。
二分查找法(了解即可,所以我没看)
3. 动态规划答疑篇
都是一些基础技巧,暂时不想看。直接一边写题,不懂了再看
。。。。还有几篇
6. 对动态规划进行降维打击:空间压缩技巧
滚动变量进行优化,缩小内存。当然你也可以不做优化,因为优化会使可读性降低。
二、子序列类型问题
1. 经典动态规划:编辑距离
[72] 编辑距离-dp数组
用的dp数组,自底向上,for循环一个一个网上推。
[72] 编辑距离-自顶向下-备忘录-递归-map<pair<int,int>,int>-memo[{i,j}]
加了一个hashmap后如此不同,是什么不同,首先,return在最后return,递归的话,先是=,最后再是return,其次,键是pair的话如何写,要弄清楚。
2. 二维递增子序列:信封嵌套问题
第一个嵌套过了,第二个没过,卡在这里了,然后怎么办。
[53] 最大子数组和-vector dp(n);//一个初始化
滑动窗口:
滑动窗口算法就是专门处理子串/子数组问题的。
dp的定义:
如果是以nums[0…i] 中的「最大的子数组和」为 dp[i]。行不通。因为,最大数组和和最后一位可能不相连。
以 nums[i] 为结尾的「最大子数组和」为 dp[i]。
有后效性是指,前面的子问题包含不确定性,后面的子问题也有不确定性?
无后效性:前面的子问题计算完后结果不再改变。
分治法:
不想看分治法
3. 经典动态规划:最长公共子序列
[1143] 最长公共子序列
二维动态数组
三、背包类型问题
2. 子集背包问题
[LCR 101.分割等和子集]-二维vector压缩一维-背包问题
一道简化的背包问题,只有重量没有质量,要恰好等于sum/2为true。
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];//其中nums[i-1]对应位置与dp[i]中一致。
压缩为一维从后向前迭代。
3. 完全背包问题
[518. 零钱兑换 II] - 完全背包问题 - 零钱面值相同的无限量
dp[j]=dp[j]+dp[j-coins[i-1]];