练习

知识点

二叉树遍历

前序遍历先访问根节点,再前序遍历左子树,再前序遍历右子树
中序遍历:先中序遍历左子树,再访问根节点,再中序遍历右子树
后序遍历:先后序遍历左子树,再后序遍历右子树,再访问根节点

注意点

  • 以根访问顺序决定是什么遍历
  • 左子树都是优先右子树

前序递归

  1. function preorderTraversal(root) {
  2. if (root == null) {
  3. return
  4. }
  5. // 先访问根再访问左右
  6. console.log(root.Val)
  7. preorderTraversal(root.Left)
  8. preorderTraversal(root.Right)
  9. }

前序非递归

中序非递归

后序非递归

注意点

  • 核心就是:根节点必须在右节点弹出之后,再弹出

DFS 深度搜索-从上到下

  1. var preoderTraversal = function(root){
  2. let result = [];
  3. dfs(root,result);
  4. return result;
  5. }
  6. var dfs = function(root,result){
  7. if( root == null ) return;
  8. result.push(root.val);
  9. dfs(root.left, result);
  10. dfs(root.right, result);
  11. }

DFS 深度搜索-从下向上-分治法

  1. var preorderTraversal = function(root){
  2. result = divideAndConquer(root);
  3. return result;
  4. }
  5. var divideAndConquer = function(root) {
  6. let result = [];
  7. if(root == null) return result;
  8. let left = divideAndConquer(root.left);
  9. let right = divideAndConquer(root.right);
  10. result.push(root.val);
  11. result.push(left);
  12. result.push(right);
  13. return result;
  14. }

注意点:

DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并

BFS 层次遍历

分治法应用

归并排序,典型的分治算法;分治,典型的递归结构。

适用场景

  • 快速排序
  • 归并排序
  • 二叉树相关问题

分治算法可以分三步走:分解 -> 解决 -> 合并

  1. 分解原问题为结构相同的子问题。
  2. 分解到某个容易求解的边界之后,进行第归求解。
  3. 将子问题的解合并成原问题的解。
  1. function traversal(root){
  2. if(root == null) {
  3. // do something and return
  4. }
  5. // 分解
  6. let left = traversal(root.Left)
  7. let right = traversal(root.Right)
  8. // 解决 合并
  9. let result = Merge from left and right
  10. return result
  11. }

典型示例

  1. // V2:通过分治法遍历二叉树
  2. function preorderTraversal(root) {
  3. let result = divideAndConquer(root)
  4. return result
  5. }
  6. function divideAndConquer(root *TreeNode) []int {
  7. let result = [];
  8. // 返回条件
  9. if (root == nil) {
  10. return result
  11. }
  12. // 分治(Divide)
  13. let left = divideAndConquer(root.Left)
  14. let right = divideAndConquer(root.Right)
  15. // 合并结果(Conquer)
  16. result.push(root.val)
  17. result.push(left);
  18. result.push(right)
  19. return result
  20. }

归并排序

  1. func MergeSort(nums []int) []int {
  2. return mergeSort(nums)
  3. }
  4. func mergeSort(nums []int) []int {
  5. if len(nums) <= 1 {
  6. return nums
  7. }
  8. // 分治法:divide 分为两段
  9. mid := len(nums) / 2
  10. left := mergeSort(nums[:mid])
  11. right := mergeSort(nums[mid:])
  12. // 合并两段数据
  13. result := merge(left, right)
  14. return result
  15. }
  16. func merge(left, right []int) (result []int) {
  17. // 两边数组合并游标
  18. l := 0
  19. r := 0
  20. // 注意不能越界
  21. for l < len(left) && r < len(right) {
  22. // 谁小合并谁
  23. if left[l] > right[r] {
  24. result = append(result, right[r])
  25. r++
  26. } else {
  27. result = append(result, left[l])
  28. l++
  29. }
  30. }
  31. // 剩余部分合并
  32. result = append(result, left[l:]...)
  33. result = append(result, right[r:]...)
  34. return
  35. }

注意点

递归需要返回结果用于合并

快速排序

注意点:

快排由于是原地交换所以没有合并过程 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃

常见题目示例

maximum-depth-of-binary-tree

maximum-depth-of-binary-tree

给定一个二叉树,找出其最大深度。

思路:分治法

  1. var maxDepth = function(root) {
  2. if(root == null) return 0;
  3. let left = maxDepth(root.left);
  4. let right = maxDepth(root.right);
  5. return Math.max(left,right) + 1;
  6. };

balanced-binary-tree

balanced-binary-tree

给定一个二叉树,判断它是否是高度平衡的二叉树。

思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1,
因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据,
所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。

  1. var isBalanced = function(root) {
  2. if(root == null) return true;
  3. var left = isBalanced(root.left);
  4. var right = isBalanced(root.right);
  5. return Math.abs(helper(root.left) - helper(root.right) ) < 2 && left && right
  6. };
  7. var helper = function(root) {
  8. if(root == null) return 0;
  9. let left = helper(root.left);
  10. let right = helper(root.right);
  11. return Math.max(left,right) + 1;
  12. }

注意

一般工程中,结果通过两个变量来返回,不建议用一个变量表示两种含义

binary-tree-maximum-path-sum

binary-tree-maximum-path-sum

给定一个非空二叉树,返回其最大路径和。

思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可

  1. var maxPathSum = function(root) {
  2. if(root == null) return 0;
  3. let max = -99999999999;
  4. function helper(root){
  5. if(root == null) return 0;
  6. let left = Math.max(helper(root.left),0);
  7. let right = Math.max(helper(root.right),0);
  8. max = Math.max(left+right+root.val , max);
  9. return Math.max(left,right) + root.val;
  10. };
  11. helper(root);
  12. return max
  13. };

lowest-common-ancestor-of-a-binary-tree

lowest-common-ancestor-of-a-binary-tree

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点

  1. var lowestCommonAncestor = function(root, p, q) {
  2. if(root == null || root === p || root == q) return root;
  3. let left = lowestCommonAncestor(root.left, p, q);
  4. let right = lowestCommonAncestor(root.right, p,q);
  5. if(left == null) return right;
  6. if(right == null) return left;
  7. return root;
  8. };

BFS 层次应用

binary-tree-level-order-traversal

binary-tree-level-order-traversal

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)

思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O(logN))

  1. var levelOrder = function(root) {
  2. let result = [];
  3. if(root === null) return result;
  4. let queue = [];
  5. queue.push({node: root, leval:0})
  6. while(queue.length) {
  7. let { node, leval} = queue.shift();
  8. if(!result[leval]) {
  9. result[leval] = []
  10. }
  11. result[leval].push(node.val);
  12. if(node.left) queue.push({node: node.left, leval: leval+1});
  13. if(node.right) queue.push({node: node.right, leval: leval+1})
  14. }
  15. return result;
  16. };

binary-tree-level-order-traversal-ii

binary-tree-level-order-traversal-ii

给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

思路:在层级遍历的基础上,翻转一下结果即可

  1. var levelOrderBottom = function(root) {
  2. let result = [];
  3. if(root === null) return result;
  4. let queue = [];
  5. queue.push({node: root, leval:0})
  6. while(queue.length) {
  7. let { node, leval} = queue.shift();
  8. if(!result[leval]) {
  9. result[leval] = []
  10. }
  11. result[leval].push(node.val);
  12. if(node.left) queue.push({node: node.left, leval: leval+1});
  13. if(node.right) queue.push({node: node.right, leval: leval+1})
  14. }
  15. return result.reverse();
  16. };

binary-tree-zigzag-level-order-traversal

binary-tree-zigzag-level-order-traversal

给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历

  1. var zigzagLevelOrder = function(root) {
  2. let result = [];
  3. if(root === null) return result;
  4. let queue = [];
  5. queue.push({node: root, leval:0})
  6. while(queue.length) {
  7. let { node, leval} = queue.shift();
  8. if(!result[leval]) {
  9. result[leval] = []
  10. }
  11. if(leval % 2 ){
  12. result[leval].unshift(node.val);
  13. } else {
  14. result[leval].push(node.val);
  15. }
  16. if(node.left) queue.push({node: node.left, leval: leval+1});
  17. if(node.right) queue.push({node: node.right, leval: leval+1})
  18. }
  19. return result;
  20. };

二叉搜索树应用

validate-binary-search-tree

validate-binary-search-tree

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

思路 1:中序遍历,检查结果列表是否已经有序

思路 2:分治法,判断左 MAX < 根 < 右 MIN

  1. var isValidBST = function(root) {
  2. var helper = function(root, max, min){
  3. if(root == null) return true;
  4. if(max !== null && root.val >= max.val) return false;
  5. if(min !== null && root.val <= min.val) return false;
  6. return helper(root.left, root,min) && helper(root.right, max, root);
  7. };
  8. return helper(root,null,null);
  9. };

insert-into-a-binary-search-tree

insert-into-a-binary-search-tree

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。

思路:找到最后一个叶子节点满足插入条件即可

  1. var insertIntoBST = function(root, val) {
  2. if(root == null) {
  3. return new TreeNode(val)
  4. } else if (root.val > val) {
  5. root.left = insertIntoBST(root.left,val);
  6. } else {
  7. root.right = insertIntoBST(root.right, val);
  8. }
  9. return root;
  10. };

总结

  • 掌握二叉树递归与非递归遍历
  • 理解 DFS 前序遍历与分治法
  • 理解 BFS 层次遍历