活跃农民
- 积分
- 634
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-6-9
- 最后登录
- 1970-1-1
|
回归地里面的打卡贴
最近一个月和其它3个小伙伴一起开了一个google doc相互监督刷题进度,就没有怎么来地里做总结。 现在基本做完了所有的Tree 的题目以及小半部分的DP的题目,开一个阶段总结贴梳理一下Tree的知识点
参考总结 :
Tree总结1
1. 先是最基础的Tree的遍历,包括前序(preorder), 中序(inorder), 后序(postorder),Tree的遍历是后面的进阶题的基础,因为遍历的过程中可以同时对Tree的每个节点做一些额外的操作例如 加和,比较,变换位置等等
递归版本的遍历十分简单,只需要注意必须是边走边加即可,需要单独开一个helper(TreeNode root, List<Integer> res) function使用递归来做处理
非递归的版本的遍历,难度依次增加,最简单的是Preorder:
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
下一难度的是inorder traversal, 有一个小的trick是在遍历到左下的节点时,要有一步check右节点操作,如果右子节点存在左子树,那么要继续遍历左子树, 如果没有右子节点,那么该节点就直接作为第一个输出的节点即可,将其加入到最后的res List当中并向上返回。用一个两个限定条件的while loop来完成这件事
- while (!stack.isEmpty() || head != null) {
- if (head != null) {
- stack.push(head);
- head = head.left;
- } else {
- head = stack.pop();
- res.add(head);
- head = head.right;
- }
复制代码
最后是最难的postorder 遍历
有两种实现方法,分别使用了两个栈和一个栈,先说使用两个栈的方法,最基本的思路是一个栈做最简单的preorder,另一个栈负责将preorder的结果倒序
- //postorder-two stack
- Stack<TreeNode> s1 = new Stack<TreeNode>();
- Stack<TreeNode> s2 = new Stack<TreeNode>();
- s1.push(head);
- while (!s1.isEmpty()) {
- head = s1.pop();
- s2.push(head);
- //注意这个地方顺序与preorder相反,因为s2会倒序,所以这里是先加node.left再加node.right,而preorder是先加node.right, 再加node.left
- if (head.left != null) {
- s1.push(head.left);
- }
- if (head.right != null) {
- s1.push(head.right);
- }
- }
- while(!s2.isEmpty()) {
- res.add(s2.pop());
- }
复制代码
下面是使用一个栈来实现postorder,其中的head指针表示最近一次弹出并加入到res List当中的节点,curr表示的是stack的栈顶节点,也就是当前可能马上要打印的节点,先走左,不行再走右,还不行就打印curr, top down的走法
- //postorder-one stack
- if (head != null) {
- Stack<TreeNode> stack = new Stack<TreeNode>();
- stack.push(head);
- TreeNode curr = null;
- while (!stack.isEmpty()) {
- curr = stack.peek();
- if (curr.left != null && head != curr.left && head != curr.right) {
- stack.push(curr.left);
- } else if (curr.right != null && head != curr.right) {
- stack.push(curr.right);
- } else {
- res.add(stack.pop());
- head = curr;
- }
- }
- }
复制代码
有了Tree的遍历做基础,来看几道用遍历+节点操作就可以秒杀的题目:
Inorder 可以和BST的特性结合来构造有序的数组
297. Serialize and Deserialize Binary Tree 直接一个Preorder + Queue数据结构记录节点,注意对空节点的处理 "#"
538. Convert BST to Greater Tree 直接声明一个全局变量sum来记录当前走过的所有greater node的sum + 一个右中左的中序遍历即可
617. Merge Two Binary Trees 4种情况分类讨论即可: 1. t1 == null && t2 == null 2.t1 == null 3.t2 == null 4.t1 != null && t2 != null
108. Convert Sorted Array to Binary Search Tree 直接一个Inorder + 数组二分 注意递归的helper function 终点 if (start > end) return null;
230. Kth Smallest Element in a BST 设全局变量,直接Inorder 遍历解决
814. Binary Tree Pruning 类似于postorder 的处理手法,左右中 prune的条件是(root.left == null && root.right == null && root.val == 0)
有点小难度的遍历类题目:
106. Construct Bianry Tree from Inorder and Postorder Traversal
105. Construct Binary Tree from Preorder and Inorder Traversal
这两道题的相似性在于使用Preorder 和 Postorder 数组来构造树的节点,利用inorder来确定每棵树的范围
173. Binary Search Tree Iterator
285. Inorder Successor in BST
这两道题本质上是同一道题,都是求BST的inorder 的下一个节点,可以用两个指针 或者辅助栈来做。如果用双指针,则声明一个新的pre指针来记录当前位置的父节点,先遍历到最左下作为起点,然后找后继节点需要分类讨论,如果当前节点位于pre节点的左边,那么当前节点的right subtree会存在后继,如果不存在right subtree,那么当前节点的parent节点就可以直接作为后继节点。
Divide & Conquer 思路
687. Longest Univalue Path 分别先算Left 和 right 的深度,如果root.val == val -> Math.max(left, right) + 1; else return 0 有一个Trick的地方是在helper()的递归function当中,有一个三级结构的存在helper 当中的val是父节点的value, 向下传递的是当前节点的root.val, left = helper(root.left, root.val) , 而得到的left/right 结果是下面子节点的返回结果
543. Diameter of Binary Tree 687的简化版,不需要判断root.val == val直接做加减
669. Trim a Binary Search Tree 判断落点位置,root.val < L, 往右走(右子树都大于root), > L && < R (保留当前节点 root.left = Trim(root.left, L, R), root.right = Trim(root.right, L, R), > R, 往左走
112. Path Sum 是否存在root-> left的路径 == 只需要找到一条路径即可,递归的function只需要保证每次都减掉当前的root.val即可
113. Path Sum II 同样是找root-> left的路径,但是需要打印所有的路径,所以是一个exhuastive search 需要用到回溯的算法,每个节点都是潜在的添加值,所以每个节点都应当添加到curr当前路径当中,但是只有符合出口要求的curr路径才能被添加到res总的路径list当中 helper(TreeNode root, List<List<Integer>> res, List<Integer> curr, int sum)
437. Path Sum III 每一个TreeNode 节点都可以成为一个path的起点,写helper() 返回的是以这个点作为起点的path数量,那么对于每个点分别要有3个helper() 对应以当前点位起点,当前点的left节点,当前点的right节点
101. Symmetric Tree
100. Same Tree
110. Balanced Binary Tree 用ResultType 包装类来分别存depth + isBalanced 两个变量
226. Invert Binary Tree 左换右, 右换左,注意用temp变量来暂时存一下
236. LCA of Binary Tree 何时退出找到就退出,可能是Left, 也可能是root, 也可能是right, 判断上下级关系
257. Binary Tree Paths 操作的时候要考虑四种情况if(root == null) if (currPath == "") else{...} if (root.left == null && root.right == null)
Path 类型的题目:
全局最大是一定要考虑当前root.val + left + right, 但是function本身返回的应该是左或右两支中较大的一支
TOP-> DOWN BOTTOM -> UP 都有可能,根据递归与操作的出现顺序的前后来调整 先递归 再操作是bottom-up, 先操作再递归是top-down
Level Order 遍历思路 注意while(!queue.isEmpty()) 是对于整个树的多个level遍历,for(int i = 0; i < size; i++) 是对当前的一个level的处理
637. Average of Levels in Binary Tree 注意使用double 数据类型,因为会存在小数
515. Find Largest Value in Each Tree Row
513. Find Bottom Left Tree Value 一个逆序的level order直接解决
199. Binary Tree Right Side View 记录每一层的最右边的节点
116. Populating Next Right Pointers in Each Node 设置一个pre节点来指向左边的节点,每次pop()出来的节点作为当前的节点pre.next指向当前节点
小Trick 题:
222. Count Complete Tree Nodes 根据完全二叉树的性质使用位运算来加快速度
待补充:
链表 + Tree
DP + Tree
|
|