【算法总结】30道题搞定大厂算法面试-二叉树
[TOC]
前言
前段时间,写了面试必备的一系列文章,反应还不错。有一些读者反馈说,能不能整理一些面试常见的算法。前段时间,我恰好总结了 LeetCode 常见的面试算法题目。
Android 面试必备 - http 与 https 协议
Android 面试必备 - 计算机网络基本知识(TCP,UDP,Http,https)
Android 面试必备 - 线程
Android 面试必备 - JVM 及 类加载机制
Android 面试必备 - 系统、App、Activity 启动过程
面试官系列- 你真的了解 http 吗
面试官问, https 真的安全吗,可以抓包吗,如何防止抓包吗
java 版剑指offer算法集锦
LeetCode链表知识点&题型总结
Android_interview github 地址
刚开始准备刷算法题目的时候,感觉真的是好难,十道题目有九道是不会的。心中曾一万只草泥马跑过,自己怎么这么辣鸡。
慢慢得,我发现算法也是一个可以通过练习慢慢成长的。
- 首先我们要掌握基本的数据结构,数组,链表,哈希表, Set,二叉树,堆,栈等。你要知道他们有什么优缺点,适应场景是什么,时间复杂度和空间复杂度是多少。而不能知道简单的 API。
- 接着,掌握了这些基本的数据结构之后,一些基本的算法你也要掌握以下,比如快速排序,归并排序,对排序,二分查找。这些基本的一定要掌握,面试当中经常也会问到。
- 分类刷题,我们在力扣上面可以看到,https://leetcode-cn.com/problemset/algorithms/ ,刷题是可以按标签来的。比如链表,数组,二分查找,二叉树,动态规划等
- 学好算法不是一日之功,需要长期的积累。建议的做法是每天做一两道题,题目不在多,贵在于理解。坚持一两个月,你会发现你的感觉逐渐好起来了
废话不多说了,开始进入今天的正文,LeetCode链表知识点&题型总结
二叉树的概念
二叉树(Binary Tree)是包含n个节点的有限集合,该集合或者为空集(此时,二叉树称为空树),或者由一个根节点和两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
一棵典型的二叉树如下图所示:
[图片上传失败...(image-7cc59b-1609823067409)]
由上述的定义可以看出,二叉树中的节点至多包含两棵子树,分别称为左子树和右子树,而左子树和右子树又分别至多包含两棵子树。由上述的定义,二叉树的定义是一种递归的定义。
二叉树种类
满二叉树
对于一棵二叉树,如果每一个非叶子节点都存在左右子树,并且二叉树中所有的叶子节点都在同一层中,这样的二叉树称为满二叉树。
完全二叉树
对于一棵具有n个节点的二叉树按照层次编号,同时,左右子树按照先左后右编号,如果编号为i的节点与同样深度的满二叉树中编号为i的节点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树。
二叉排序树:
又称二叉查找树(Binary Search Tree),亦称二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的节点
二分查找的时间复杂度是O(log(n)),最坏情况下的时间复杂度是O(n)(相当于顺序查找)
平衡二叉树:
又称 AVL 树。平衡二叉树是二叉搜索树的进化版,所谓平衡二叉树指的是,左右两个子树的高度差的绝对值不超过 1。
红黑树:
红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是一种查找树。红黑树有一个重要的性质,从根节点到叶子节点的最长的路径不多于最短的路径的长度的两倍。对于红黑树,插入,删除,查找的复杂度都是O(log N)。
哈夫曼树:
给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。
遍历方式
二叉树主要有四种遍历方式
- 先序(先根)遍历:即先访问根节点,再访问左孩子和右孩子
- 中序遍历:先访问做孩子,再访问根节点和右孩子
- 后序遍历:先访问左孩子,再访问右孩子,再访问根节点
- 层次遍历:按照所在层数,从下往上遍历
前提:这里先给出测试中的二叉树结构,如下图所示
[图片上传失败...(image-98676f-1609823067409)]
该二叉树对应的几种遍历方式的结果顺序:
- 先序遍历:10->6->4->8->14->12->16
- 中序遍历:4->6->8->10->12->14->16
- 后序遍历:4->8->6->12->16->14->10
- 层次遍历:10->6->14->4->8->12->16
递归
一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。
1. 树的高度
1.0 求二叉树的最大层数(最大深度)
104. Maximum Depth of Binary Tree (Easy)
Leetcode / 力扣
这道题目的解法其实很简单
- 如果二叉树为空,二叉树的深度为0
- 如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
public int maxDepth(TreeNode root) { if (root == null) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;}
1.1 二叉树的最小深度
LeetCode:Minimum Depth of Binary Tree
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
[图片上传失败...(image-1e1561-1609823067409)]
class Solution { public int minDepth(TreeNode root) { if(root == null) return 0; int left = minDepth(root.left); int right = minDepth(root.right); return (left == 0 || right == 0) ? left + right + 1 : Math.min(left, right) + 1; }}
2. 平衡树
110. Balanced Binary Tree (Easy)
Leetcode / 力扣
3 / \ 9 20 / \ 15 7
思路
平衡树左右子树高度差都小于等于 1
private boolean result = true;public boolean isBalanced(TreeNode root) { maxDepth(root); return result;}public int maxDepth(TreeNode root) { if (root == null) return 0; int l = maxDepth(root.left); int r = maxDepth(root.right); if (Math.abs(l - r) > 1) result = false; return 1 + Math.max(l, r);}
3. 两节点的最长路径
543. Diameter of Binary Tree (Easy)
Leetcode / 力扣
Input: 1 / \ 2 3 / \ 4 5Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].
private int max = 0;public int diameterOfBinaryTree(TreeNode root) { depth(root); return max;}private int depth(TreeNode root) { if (root == null) return 0; int leftDepth = depth(root.left); int rightDepth = depth(root.right); max = Math.max(max, leftDepth + rightDepth); return Math.max(leftDepth, rightDepth) + 1;}
4. 翻转树
226. Invert Binary Tree (Easy)
Leetcode / 力扣
public TreeNode invertTree(TreeNode root) { if (root == null) return null; TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来 root.left = invertTree(root.right); root.right = invertTree(left); return root;}
5. 归并两棵树
617. Merge Two Binary Trees (Easy)
Leetcode / 力扣
Input: Tree 1 Tree 2 1 2 / \ / \ 3 2 1 3 / \ \ 5 4 7Output: 3 / \ 4 5 / \ \ 5 4 7
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return null; if (t1 == null) return t2; if (t2 == null) return t1; TreeNode root = new TreeNode(t1.val + t2.val); root.left = mergeTrees(t1.left, t2.left); root.right = mergeTrees(t1.right, t2.right); return root;}
6. 判断路径和是否等于一个数
Leetcdoe : 112. Path Sum (Easy)
Leetcode / 力扣
Given the below binary tree and sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ \ 7 2 1return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.
路径和定义为从 root 到 leaf 的所有节点的和。
public boolean hasPathSum(TreeNode root, int sum) { if (root == null) return false; if (root.left == null && root.right == null && root.val == sum) return true; return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);}
7. 统计路径和等于一个数的路径数量
437. Path Sum III (Easy)
Leetcode / 力扣
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 10 / \ 5 -3 / \ \ 3 2 11 / \ \3 -2 1Return 3. The paths that sum to 8 are:1. 5 -> 32. 5 -> 2 -> 13. -3 -> 11
路径不一定以 root 开头,也不一定以 leaf 结尾,但是必须连续。
public int pathSum(TreeNode root, int sum) { if (root == null) return 0; int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); return ret;}private int pathSumStartWithRoot(TreeNode root, int sum) { if (root == null) return 0; int ret = 0; if (root.val == sum) ret++; ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val); return ret;}
8. 子树
572. Subtree of Another Tree (Easy)
Leetcode / 力扣
Given tree s: 3 / \ 4 5 / \ 1 2Given tree t: 4 / \ 1 2Return true, because t has the same structure and node values with a subtree of s.Given tree s: 3 / \ 4 5 / \ 1 2 / 0Given tree t: 4 / \ 1 2Return false.
public boolean isSubtree(TreeNode s, TreeNode t) { if (s == null) return false; return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);}private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) { if (t == null && s == null) return true; if (t == null || s == null) return false; if (t.val != s.val) return false; return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);}
9. 树的对称
101. Symmetric Tree (Easy)
Leetcode / 力扣
[图片上传失败...(image-170791-1609823067409)]
public boolean isSymmetric(TreeNode root) { if (root == null) return true; return isSymmetric(root.left, root.right);}private boolean isSymmetric(TreeNode t1, TreeNode t2) { if (t1 == null && t2 == null) return true; if (t1 == null || t2 == null) return false; if (t1.val != t2.val) return false; return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);}
10 求二叉树的镜像
[图片上传失败...(image-857f6b-1609823067409)]
class Solution { public TreeNode invertTree(TreeNode root) { if(root == null) return root; // 先保存最节点,因为 invertTree 已经变化了 TreeNode node = root.left; root.left = invertTree(root.right); root.right = invertTree(node); return root; }}
11. 最小路径
111. Minimum Depth of Binary Tree (Easy)
Leetcode / 力扣
树的根节点到叶子节点的最小路径长度
public int minDepth(TreeNode root) { if (root == null) return 0; int left = minDepth(root.left); int right = minDepth(root.right); if (left == 0 || right == 0) return left + right + 1; return Math.min(left, right) + 1;}
层次遍历
使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。
1. 二叉树的层序遍历
二叉树的层序遍历 II
给定二叉树,返回其节点值的自下而上级别顺序遍历。
class Solution { public List> levelOrderBottom(TreeNode root) { List> res = new LinkedList<>(); Queue queue = new LinkedList<>(); if(root == null) return res; queue.add(root); while(!queue.isEmpty()){ int count = queue.size(); List temp = new LinkedList<>(); for(int i=0; i
按之字形打印二叉树
剑指offer:按之字形顺序打印二叉树
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
- 设两个栈,s2存放奇数层,s1存放偶数层
- 遍历s2节点的同时按照左子树、右子树的顺序加入s1,
- 遍历s1节点的同时按照右子树、左子树的顺序加入s2
import java.util.ArrayList;import java.util.Stack;/*public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { public ArrayList > Print(TreeNode pRoot) { ArrayList > res = new ArrayList >(); Stack s1 = new Stack(); Stack s2 = new Stack(); int flag = 1; if(pRoot == null) return res; s2.push(pRoot); ArrayList temp = new ArrayList(); while(!s1.isEmpty() || !s2.isEmpty()){ if(flag % 2 != 0){ while(!s2.isEmpty()){ TreeNode node = s2.pop(); temp.add(node.val); if(node.left != null){ s1.push(node.left); } if(node.right != null){ s1.push(node.right); } } } if(flag % 2 == 0){ while(!s1.isEmpty()){ TreeNode node = s1.pop(); temp.add(node.val); if(node.right != null){ s2.push(node.right); } if(node.left != null){ s2.push(node.left); } } } res.add(new ArrayList(temp)); temp.clear(); flag ++; } return res; }}
前中后序遍历
1 / \ 2 3 / \ \4 5 6
- 层次遍历顺序:[1 2 3 4 5 6]
- 前序遍历顺序:[1 2 4 5 3 6]
- 中序遍历顺序:[4 2 5 1 3 6]
- 后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
① 前序
void dfs(TreeNode root) { visit(root); dfs(root.left); dfs(root.right);}
② 中序
void dfs(TreeNode root) { dfs(root.left); visit(root); dfs(root.right);}
③ 后序
void dfs(TreeNode root) { dfs(root.left); dfs(root.right); visit(root);}
1. 非递归实现二叉树的前序遍历
144. Binary Tree Preorder Traversal (Medium)
Leetcode / 力扣
public List preorderTraversal(TreeNode root) { List ret = new ArrayList<>(); Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); if (node == null) continue; ret.add(node.val); stack.push(node.right); // 先右后左,保证左子树先遍历 stack.push(node.left); } return ret;}
2. 非递归实现二叉树的后序遍历
145. Binary Tree Postorder Traversal (Medium)
Leetcode / 力扣
前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。
public List postorderTraversal(TreeNode root) { List ret = new ArrayList<>(); Stack stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); if (node == null) continue; ret.add(node.val); stack.push(node.left); stack.push(node.right); } Collections.reverse(ret); return ret;}
3. 非递归实现二叉树的中序遍历
94. Binary Tree Inorder Traversal (Medium)
Leetcode / 力扣
public List inorderTraversal(TreeNode root) { List ret = new ArrayList<>(); if (root == null) return ret; Stack stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { while (cur != null) { stack.push(cur); cur = cur.left; } TreeNode node = stack.pop(); ret.add(node.val); cur = node.right; } return ret;}
BST
二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。
二叉查找树中序遍历有序。
1. 修剪二叉查找树
669. Trim a Binary Search Tree (Easy)
Leetcode / 力扣
Input: 3 / \ 0 4 \ 2 / 1 L = 1 R = 3Output: 3 / 2 / 1
题目描述:只保留值在 L ~ R 之间的节点
public TreeNode trimBST(TreeNode root, int L, int R) { if (root == null) return null; if (root.val > R) return trimBST(root.left, L, R); if (root.val < L) return trimBST(root.right, L, R); root.left = trimBST(root.left, L, R); root.right = trimBST(root.right, L, R); return root;}
2. 寻找二叉查找树的第 k 个元素
230. Kth Smallest Element in a BST (Medium)
Leetcode / 力扣
中序遍历解法:
private int cnt = 0;private int val;public int kthSmallest(TreeNode root, int k) { inOrder(root, k); return val;}private void inOrder(TreeNode node, int k) { if (node == null) return; inOrder(node.left, k); cnt++; if (cnt == k) { val = node.val; return; } inOrder(node.right, k);}
递归解法:
public int kthSmallest(TreeNode root, int k) { int leftCnt = count(root.left); if (leftCnt == k - 1) return root.val; if (leftCnt > k - 1) return kthSmallest(root.left, k); return kthSmallest(root.right, k - leftCnt - 1);}private int count(TreeNode node) { if (node == null) return 0; return 1 + count(node.left) + count(node.right);}
3. 把二叉查找树每个节点的值都加上比它大的节点的值
Convert BST to Greater Tree (Easy)
Leetcode / 力扣
Input: The root of a Binary Search Tree like this: 5 / \ 2 13Output: The root of a Greater Tree like this: 18 / \ 20 13
先遍历右子树。
private int sum = 0;public TreeNode convertBST(TreeNode root) { traver(root); return root;}private void traver(TreeNode node) { if (node == null) return; traver(node.right); sum += node.val; node.val = sum; traver(node.left);}
4. 二叉查找树的最近公共祖先
235. Lowest Common Ancestor of a Binary Search Tree (Easy)
Leetcode / 力扣
_______6______ / \ ___2__ ___8__ / \ / \0 4 7 9 / \ 3 5For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); return root;}
5. 二叉树的最近公共祖先
236. Lowest Common Ancestor of a Binary Tree (Medium)
Leetcode / 力扣
_______3______ / \ ___5__ ___1__ / \ / \6 2 0 8 / \ 7 4For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); return left == null ? right : right == null ? left : root;}
6. 从有序数组中构造二叉查找树
108. Convert Sorted Array to Binary Search Tree (Easy)
Leetcode / 力扣
public TreeNode sortedArrayToBST(int[] nums) { return toBST(nums, 0, nums.length - 1);}private TreeNode toBST(int[] nums, int sIdx, int eIdx){ if (sIdx > eIdx) return null; int mIdx = (sIdx + eIdx) / 2; TreeNode root = new TreeNode(nums[mIdx]); root.left = toBST(nums, sIdx, mIdx - 1); root.right = toBST(nums, mIdx + 1, eIdx); return root;}
7. 根据有序链表构造平衡的二叉查找树
109. Convert Sorted List to Binary Search Tree (Medium)
Leetcode / 力扣
Given the sorted linked list: [-10,-3,0,5,9],One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST: 0 / \ -3 9 / / -10 5
public TreeNode sortedListToBST(ListNode head) { if (head == null) return null; if (head.next == null) return new TreeNode(head.val); ListNode preMid = preMid(head); ListNode mid = preMid.next; preMid.next = null; // 断开链表 TreeNode t = new TreeNode(mid.val); t.left = sortedListToBST(head); t.right = sortedListToBST(mid.next); return t;}private ListNode preMid(ListNode head) { ListNode slow = head, fast = head.next; ListNode pre = head; while (fast != null && fast.next != null) { pre = slow; slow = slow.next; fast = fast.next.next; } return pre;}
8. 在二叉查找树中寻找两个节点,使它们的和为一个给定值
653. Two Sum IV - Input is a BST (Easy)
Leetcode / 力扣
Input: 5 / \ 3 6 / \ \2 4 7Target = 9Output: True
使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。
应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。
public boolean findTarget(TreeNode root, int k) { List nums = new ArrayList<>(); inOrder(root, nums); int i = 0, j = nums.size() - 1; while (i < j) { int sum = nums.get(i) + nums.get(j); if (sum == k) return true; if (sum < k) i++; else j--; } return false;}private void inOrder(TreeNode root, List nums) { if (root == null) return; inOrder(root.left, nums); nums.add(root.val); inOrder(root.right, nums);}
9. 在二叉查找树中查找两个节点之差的最小绝对值
530. Minimum Absolute Difference in BST (Easy)
Leetcode / 力扣
Input: 1 \ 3 / 2Output:1
利用二叉查找树的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。
private int minDiff = Integer.MAX_VALUE;private TreeNode preNode = null;public int getMinimumDifference(TreeNode root) { inOrder(root); return minDiff;}private void inOrder(TreeNode node) { if (node == null) return; inOrder(node.left); if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val); preNode = node; inOrder(node.right);}
10. 寻找二叉查找树中出现次数最多的值
501. Find Mode in Binary Search Tree (Easy)
Leetcode / 力扣
1 \ 2 / 2return [2].
答案可能不止一个,也就是有多个值出现的次数一样多。
private int curCnt = 1;private int maxCnt = 1;private TreeNode preNode = null;public int[] findMode(TreeNode root) { List maxCntNums = new ArrayList<>(); inOrder(root, maxCntNums); int[] ret = new int[maxCntNums.size()]; int idx = 0; for (int num : maxCntNums) { ret[idx++] = num; } return ret;}private void inOrder(TreeNode node, List nums) { if (node == null) return; inOrder(node.left, nums); if (preNode != null) { if (preNode.val == node.val) curCnt++; else curCnt = 1; } if (curCnt > maxCnt) { maxCnt = curCnt; nums.clear(); nums.add(node.val); } else if (curCnt == maxCnt) { nums.add(node.val); } preNode = node; inOrder(node.right, nums);}
参考文章
Leetcode 题解 - 树
小结
如果你想学好算法,建议上面二叉树的题目都刷一下。如果你只是想应付面试,主要看一下高频的面试题目即可。
- 树的递归。比如树的深度,最小深度,树的镜像。
- 二叉树的前序遍历,中序遍历,后续遍历,记住,递归和非递归解法都要会。学会举一反三。
- 二叉树的层序遍历,递归和非递归也都要会。
- 常见的 BST 算法。二叉查找树的最近公共祖先(两个元素的,三个元素的,多个元素呢)。二叉树的最近公共祖先。
如果你觉得对你有所帮助,请帮忙 start
Android_interview github。或者关注我的微信公众号徐公码字
更多相关文章
- 我的android 第10天 - pull解析Xml文档
- [置顶] Android曙光集群发来的邀请函
- (一)Android数据结构学习之链表
- Android(安卓)APP UI卡顿的原理
- android程序获取WIFI的IP地址和MAC地址
- Android(安卓)Content Provider详解及示例代码
- android实现上下滑动
- Android(安卓)节点进度条
- PULL解析XML