二叉树算法总结
主要解决思路:
- 递归
- 自底向上分治
- 栈或队列
常见算法题
二叉树结构定义如下
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
1、前序遍历 LeetCode144
给定一个二叉树,返回它的 前序 遍历。
递归解法
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList list = new ArrayList<Integer>();
preorderHelper(root, list);
return list;
}
public void preorderHelper(TreeNode root, ArrayList list){
if (root == null)return;
list.add(root.val);
preorderHelper(root.left, list);
preorderHelper(root.right, list);
}
**注意点:**注意判空
迭代解法:
压入栈
public List<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
if (root == null)return list;
stack.push(root);
while(!stack.isEmpty()){
TreeNode visit = stack.pop();
list.add(visit.val);
if (root.right != null)stack.push(root.right);
if (root.left != null)stack.push(root.left);
}
return list;
}
序列化二叉树 #297
你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 “[1,2,3,null,null,4,5]”
public String serialize(TreeNode root) {
StringBuilder res = helper(root, new StringBuilder());
return res.toString();
}
private StringBuilder helper(TreeNode node, StringBuilder sb){
if(node == null){
sb.append("null,");
return sb;
}
sb.append(node.val);
sb.append(",");
sb = helper(node.left, sb);
sb = helper(node.right, sb);
return sb;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] array = data.split(",");
List<String> list = new LinkedList<>(Arrays.asList(array));
return rebuildTree(list);
}
private TreeNode rebuildTree(List<String> list){
if(list.get(0).equals("null")){
list.remove(0);
return null;
}
TreeNode node = new TreeNode(Integer.valueOf(list.get(0)));
list.remove(0);
node.left = rebuildTree(list);
node.right = rebuildTree(list);
return node;
}
2、中序遍历94
递归方法和前序遍历类似,只是优先展示左子树的值
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList list = new ArrayList<Integer>();
inorderHelper(root, list);
return list;
}
public void inorderHelper(TreeNode root, ArrayList list){
if (root == null)return;
inorderHelper(root.left, list);
list.add(root.val);
inorderHelper(root.right, list);
}
迭代方法
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> list = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
while (cur != null){
stack.push(cur);
cur = cur.left;
}
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
return list;
}
3、后续遍历#145
public List<Integer> postorderTraversal(TreeNode root) {
ArrayList list = new ArrayList<Integer>();
postorderHelper(root, list);
return list;
}
public void postorderHelper(TreeNode root, ArrayList list){
if (root == null)return;
postorderHelper(root.left, list);
postorderHelper(root.right, list);
list.add(root.val);
}
注意点:
4、层序遍历 #102
即逐层地,从左到右访问所有节点
Input:
3
/ \
9 20
/ \
15 7
Output: [[3],[9,20],[15,7]]
ArrayList<ArrayList<Integer>> Print(BinaryTreeNode pRoot) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
ArrayList<Integer> list = new ArrayList<Integer>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int start = 0, end = 1;
while (!queue.isEmpty()) {
//先进先出,从左到右
TreeNode node = queue.poll();
list.add(node.val);
start++;
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
//行控制 eg:end=1->2->4
if (start == end) {
end = queue.size();
start = 0;
res.add(list);
list = new ArrayList<Integer>();
}
}
return res;
}
5、打印Z字型 #面试题32
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
使用两个栈,其中一个栈(stack2)处理从左到右打印,另一个栈处理从右到左打印,stack2先放左子树再放右子树到stack中,这样stack先取右子树再取左子树,同理stack入栈顺序相反,完成Z字型打印
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<Integer>();
if (root == null) return res;
int start = 0, end = 1;
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack2.push(root);
while (!stack.isEmpty() || !stack2.isEmpty()) {
while (!stack2.isEmpty()) {
TreeNode qNode = stack2.pop();
start++;
list.add(qNode.val);
if (qNode.left != null) stack.push(qNode.left);
if (qNode.right != null) stack.push(qNode.right);
if (start == end) {
end = stack.size();
start = 0;
res.add(list);
list = new ArrayList<Integer>();
}
}
while (!stack.isEmpty()) {
TreeNode sNode = stack.pop();
start++;
list.add(sNode.val);
if (sNode.right != null) stack2.push(sNode.right);
if (sNode.left != null) stack2.push(sNode.left);
if (start == end) {
end = stack2.size();
start = 0;
res.add(list);
list = new ArrayList<Integer>();
}
}
}
return res;
}
5、包含子树
树A
3
/ \
9 20
/ \
15 7
子树B
3 20
/ \ \
9 20 7
public boolean isSubStructure(TreeNode A, TreeNode B) {
if (A == null || B == null)return false;
//从根开始匹配 左子树开始 右子树开始
return dfs(A, B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
public boolean dfs(TreeNode A, TreeNode B){
if(B == null) return true;
if(A == null) return false;
return A.val == B.val && dfs(A.left, B.left) && dfs(A.right, B.right);
}
6、镜像树 #面试题27
输入:
3
/ \
9 20
/ \
15 7
输出:
3
/ \
20 9
/ \
7 15
public TreeNode mirrorTree(TreeNode root) {
if (root == null) return root;
//叶子节点
if (root.left == null && root.right == null) return root;
//左右互换
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left != null) mirrorTree(root.left);
if (root.right != null) mirrorTree(root.right);
return root;
}
6、对称树 #面试题28
如果一棵二叉树和它的镜像一样,那么它是对称的
public boolean isSymmetric(TreeNode root) {
return root == null ? true : isSymmetric(root.left, root.right);
}
public boolean isSymmetric(TreeNode L, TreeNode R){
if(L == null && R == null)return true;
//左右子树一个存在一个不存在,或左右子树值不相等
if(L == null || R == null || L.val != R.val){
return false;
}
return isSymmetric(L.left,R.right) && isSymmetric(L.right,R.left);
}
7、路径和 #112
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
public boolean hasPathSum(TreeNode root, int sum) {
if(root == null) return false;
if(root.left == null && root.right == null && sum == root.val ) return true;
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
变形:给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束。
Input : 22
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
Output : 3 [5,4,11,2]、[5,8,4,5]、[4,11,7]
public int pathSum(TreeNode root, int sum) {
if(root == null)return 0;
// 包含子树
return helper(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
}
private int helper(TreeNode node, int sum){
if (null == node){
return 0;
}
sum -= node.val;
int count = sum == 0 ? 1 : 0;
count += helper(node.left, sum);
count += helper(node.right, sum);
return count;
}
8.1树最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
- 叶子节点的定义是左孩子和右孩子都为 null 时叫做叶子节点
- 当 root 节点左右孩子都为空时,返回 1
- 当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
- 当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值
public int minDepth(TreeNode root) {
if (root == null) return 0;
if (root.left == null) return minDepth(root.right) + 1;
if (root.right == null) return minDepth(root.left) + 1;
int leftDepth = minDepth(root.left) + 1;
int rightDepth = minDepth(root.right) + 1;
return leftDepth < rightDepth ? leftDepth : rightDepth;
}
8.2树最大深度
public int treeMaxDepth(BinaryTreeNode root) {
if (root == null) return 0;
//if (root.left == null && root.right == null) return 1;
int left = treeMaxDepth(root.left) + 1;
int right = treeMaxDepth(root.right) + 1;
return left > right ? left : right;
}
根判空 —> 左右子树判空 —> 递归加1 —> 对比最大深度
变形:给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
public boolean isBalanced(TreeNode root) {
//空树
if(root == null)return true;
return Math.abs(getHeight(root.left) - getHeight(root.right)) <= 1
&& isBalanced(root.left)
&& isBalanced(root.right);
}
private int getHeight(TreeNode node){
if(node == null)return 1;
return 1 + Math.max(getHeight(node.left), getHeight(node.right));
}
9.树的宽度
树的宽度是所有层中的最大宽度。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。
9、第K大节点 #面试题54
给定一棵二叉搜索树,请找出其中第k大的节点。
输入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
输出: 4
二叉搜索树特点是左子树值小于根节点值,右子树值大于根节点值,利用中序遍历思想,比如判断一个树是否为二叉搜索树也可用这种思想
public int kthLargest(TreeNode root, int k) {
if(root == null)return 0;
ArrayList<Integer> list = new ArrayList<>();
inorderHelper(root, list);
return list.get(list.size() - k);
}
public void inorderHelper(TreeNode node, ArrayList list){
if(node == null)return;
inorderHelper(node.left, list);
list.add(node.val);
inorderHelper(node.right, list);
}
10、从前序和中序遍历序列中构建二叉树 #剑指7
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
3
/ \
9 20
/ \
15 7
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder.length == 0 || inorder.length == 0) return null;
int rootVal = preorder[0];
TreeNode root = new TreeNode(rootVal);
for (int i = 0; i < preorder.length; i++) {
if (inorder[i] == preorder[0]) {
//包括前面不包括后面
root.left = buildTree(Arrays.copyOfRange(preorder, 1, i + 1), Arrays.copyOfRange(inorder, 0, i ));
root.right = buildTree(Arrays.copyOfRange(preorder, i + 1, preorder.length), Arrays.copyOfRange(inorder, i + 1, inorder.length));
break;
}
}
return root;
}
11-1、给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
* 如果当前结点t 大于结点p、q,说明p、q都在t 的左侧,所以它们的共同祖先必定在t 的左子树中,故从t 的左子树中继续查找;
* 如果当前结点t 小于结点p、q,说明p、q都在t 的右侧,所以它们的共同祖先必定在t 的右子树中,故从t 的右子树中继续查找;
* 如果当前结点t 满足 p <t < q,说明p和q分居在t 的两侧,故当前结点t 即为最近公共祖先;
* 而如果p是q的祖先,那么返回q的父结点,同理,如果q是p的祖先,那么返回p的父结点。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) return root;
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q);
} else if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right, p, q);
} else {
return root;
}
}
11-2、给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == root || q == root) return root;
TreeNode left = lowestCommonAncestor2(root.left, p, q);
TreeNode right = lowestCommonAncestor2(root.right, p, q);
if (left == null) {
return right;
}
if (right == null) {
return left;
}
return root;
}
12、给定一个二叉树,原地将它展开为一个单链表。#Leetcode 114
输入:
1
/ \
2 5
/ \ \
3 4 6
输出:
1
\
2
\
3
\
4
\
5
\
6
先将二叉树前序遍历形成list,然后构成新的树
public void flatten(TreeNode root) {
List<TreeNode> list = new ArrayList<TreeNode>();
preorderTraversal(root, list);
int size = list.size();
for (int i = 1; i < size; i++) {
TreeNode prev = list.get(i - 1), curr = list.get(i);
//左子树都是空
prev.left = null;
prev.right = curr;
}
}
public void preorderTraversal(TreeNode root, List<TreeNode> list) {
if (root != null) {
list.add(root);
preorderTraversal(root.left, list);
preorderTraversal(root.right, list);
}
}
13、完全二叉树判断 #Leetcode 958
给定一个二叉树,确定它是否是一个完全二叉树。
若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。(注:第 h 层可能包含 1~ 2h 个节点。)
输入:[1,2,3,4,5,6]
输出:true
解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。
public boolean isCompleteTree(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
boolean flag = false;
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node == null) {
flag = true;
continue;
}
if (flag) {
return false;
}
queue.offer(node.left);
queue.offer(node.right);
}
return true;
}
14、二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true
,否则返回 false
。假设输入的数组的任意两个数字都互不相同。
输入: [1,3,2,6,5]
输出: true
思路:数组最后一位是二叉树的根,前半部分都比根值小,后半部分比根值大,且每个部分都按照这种方式判断
注意:1、停止条件;2、记录过程中左右子树分割的下标;3、所有前半部分都比根值小,后半部分比根值大
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
public boolean recur(int[] postorder, int i, int j){
if(i >= j)return true;
int p = i;
while(postorder[j] > postorder[p])p++;
int m = p;
while(postorder[j] < postorder[p])p++;
return p == j && recur(postorder, i, m-1) && recur(postorder, m, j - 1);
}
15、N叉树的层序遍历 LeetCode 429
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
[
[1],
[3,2,4],
[5,6]
]
public List<List<Integer>> levelOrder(Node root) {
}
16、合并二叉树 617
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
public static TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) return root2;
if (root2 == null) return root1;
TreeNode merge = new TreeNode(root1.val + root2.val);
merge.left = mergeTrees(root1.left, root2.left);
merge.right = mergeTrees(root1.right, root2.right);
return merge;
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-two-binary-trees
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。