二叉树是面试必考题目,这里总结一些常见的2叉树的题目,树的定义如下:

public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } }
1 统计树的深度
非递归版本可以层次遍历,遍历是记录深度;
递归版本如下:

public int minDepth(TreeNode root) { if(root == null) return 0; return dfs(root); } public int dfs(TreeNode root){ if( root == null ) return 0; int ldep = dfs(root.left); int rdep = dfs(root.right); return (ldep > rdep ? ldep : rdep) + 1; }
变形1)判断左子树比右子树高1,则先求左子树高,再求右子树高,判断差值是否为1即可
2)求树的最小高度,比如下图为 3 ,这个也简单的修改下即可,在双分叉节点上返回最小的高度,在单分叉或没分叉的节点上返回最大的高度;
1
/
2 3
/ /
4 5 6
/
7
2 统计根节点到叶节点的路径的和( 129. Sum Root to Leaf Numbers)

public int sumNumbers(TreeNode root) { return dfs(root,0); } public int dfs(TreeNode p,int s){ if(p ==null ){ return 0; } if(p.left == null && p.right == null){ return s*10 + p.val; } return dfs(p.left, s*10 +p.val) + dfs(p.right, s*10 +p.val); }
3.统计叶节点的个数
//暂时先pass
pass
4.最近公共祖先LCA(Lowest Common Ancestor of a Binary Tree)
1)找两条到 P Q 的路径,然后找到这两条路径中最后一个相同的元素即可,注意对于BST,查找可利用BST有序的性质,对于P Q 分别做一个查找,得到路径,不必一起查找。

List<TreeNode> p_path = null; List<TreeNode> q_path = null; List<TreeNode> path = null; public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { p_path = new LinkedList<TreeNode>(); q_path = new LinkedList<TreeNode>(); path = new LinkedList<TreeNode>(); dfs(root,p,q,path,p_path,q_path); TreeNode res = root; int idx = 0 ; //找到两个列表中最后一个相同元素的方法 while(idx < p_path.size() && idx < q_path.size()){ if(p_path.get(idx) != q_path.get(idx)) break; else res = q_path.get(idx++); } return res; } public void dfs(TreeNode root, TreeNode p, TreeNode q, List<TreeNode> path, List<TreeNode> p_path, List<TreeNode> q_path ){ //注意找某节点路径的方法,什么时候回溯,什么时候更改 if(root != null){ path.add(root); if(root == p) p_path.addAll(path); if(root == q) q_path.addAll(path); if(p_path .size() > 0 && q_path .size() > 0) return ; dfs(root.left , p, q, path, p_path, q_path); dfs(root.right, p, q, path, p_path, q_path); path.remove(path.size()-1); } }
2)如果P或者Q本身不是祖先的话,节点P与节点Q的公共祖先R一定满足:P与Q分别出现在R的左右子树上,而如果P Q 其中一个是公共祖先,则返回第一个找到的节点即可。
如果当前子树既包含P,又包含Q,则当前节点为 LCA,如果只包含其中的一个则直接返回那一个,否则返回空。

//1)如果P或者Q本身不是祖先的话,节点P与节点Q的公共祖先R一定满足:P与Q分别出现在R的左右子树上. //2)如果P Q 其中一个是公共祖先,则返回第一个找到的节点。 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if( root ==null ) return null; if(root == p || root == q) return root; TreeNode left = lowestCommonAncestor(root.left ,p,q); TreeNode right = lowestCommonAncestor(root.right,p,q); if(left != null && right !=null) return root; return left == null ? right : left; }
3)对于BST 方法是这样的 根据树的有序性,所以-只需找到第一个满足 P<=R<=Q 或者 P>=R>=Q的节点皆可

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { TreeNode r = root; while(r != null){ if(p.val < r.val && q.val < r.val) r= r.left; else if(p.val > r.val && q.val > r.val) r = r.right; else break; } return r; }
5. leetcode : 117. Populating Next Right Pointers in Each Node II

/** * Definition for binary tree with next pointer. * public class TreeLinkNode { * int val; * TreeLinkNode left, right, next; * TreeLinkNode(int x) { val = x; } * } */ public class Solution { public void connect(TreeLinkNode root) { if(root == null) return ; root.next = null; dfs(root); } public void link(TreeLinkNode tail ,TreeLinkNode p){ if(tail.next != null) return; while(p != null){ if(p.left !=null && p.right != null) { tail.next = p.left ; tail = tail.next; tail.next = p.right; tail = tail.next; }else if(p.left != null){tail.next = p.left ; tail = tail.next;} else if(p.right!= null){tail.next = p.right; tail = tail.next;} p = p.next; } } //注意每次遍历,用 link 方法将 root 的下一层的节点整体串起来 public void dfs(TreeLinkNode root){ if(root == null) return; if(root.left == null && root.right == null) return; if(root.left == null){//左空 if(root.next == null) root.right.next = null; else link(root.right,root.next); }else if(root.right == null){//右空 if(root.next == null) root.left.next = null; else link(root.left,root.next); }else{ //左右均不空 root.left.next = root.right; if(root.next == null) root.right.next = null; else link(root.right,root.next); } if(root.left !=null) dfs(root.left ); if(root.right!=null) dfs(root.right); } }
6. 层次遍历
这里直接给出层次遍历的分层打印方法,用parent 与 children 两个变量分别统计当前层与下一层的节点数,每次拿出一个节点,当前层计数 parent-1;当parent == 0;输出一个换行符,然后另 parent = children ,children =0 即可。这个题对 5)有效。

public void lfs(Node root) { if(root == null) return ; //十分万能的,当队列或者栈都可以用的 LinkedList。 LinkedList<Node> queue = new LinkedList<Node>(); queue.add(root); int parent = 1; int children = 0; while(!queue.isEmpty()){ System.out.print(queue.peek() +" "); Node cur = queue.removeFirst(); --parent; if(cur.left != null) {queue.addLast(cur.left ); ++children;} if(cur.right != null) {queue.addLast(cur.right); ++children;} if(parent == 0) {System.out.println(); parent = children; children = 0;} } }
dfs 模拟 bfs:

//dfs 模拟 bfs public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res= new ArrayList<List<Integer>>(); if(root == null) return res; dfs(root,0,res); return res; } public void dfs(TreeNode root, int height, List<List<Integer>> res){ if(root == null) return; if(res.size() <= height){ res.add(new ArrayList<Integer>()); } res.get(height).add(root.val); dfs(root.left , height+1, res); dfs(root.right, height+1, res); }
7. 判断一棵二叉树是否按照中轴对称

public boolean isSymmetric(TreeNode root) { if(root == null) return true; return dfs(root.left,root.right); } public boolean dfs(TreeNode left,TreeNode right){ if(left == null || right == null) return left == right; if(left.val != right.val) return false; return dfs(left.left,right.right) && dfs(left.right,right.left); }
8. 二叉树非递归遍历
先序与中序非常的 easy,直接给代码

//pre-order public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); if(root == null) return res; LinkedList<TreeNode> s = new LinkedList<TreeNode>(); while(root != null || !s.isEmpty()){ while(root!=null){ s.push(root); res.add(root.val); root = root.left; } if(!s.isEmpty()){ root = s.pop(); root = root.right; } } return res; } //in-order public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); //结果集 LinkedList<TreeNode> s = new LinkedList<TreeNode>(); //辅助栈 if(root == null) return res; while(root != null || !s.isEmpty()){ while(root != null){ s.push(root); root = root.left; }//此时 root 已经为空了 if(!s.isEmpty()){ root = s.pop(); res.add(root.val); root = root.right; } } return res; }
后序比较困难,这里给三种方式:
法1:pre-order : root-left-right ,post-order 需要 left-right-root,所以在pre-order的时候先放right再放left得到 root-right-left ,然后反转即可,非常 tricky 的idea.

public List<Integer> postorderTraversal(TreeNode root) { LinkedList<Integer> res = new LinkedList<Integer>(); LinkedList<TreeNode> s = new LinkedList<TreeNode>(); if(root == null) return res; while(root != null || !s.isEmpty()){ while(root != null){ s.push(root); res.add(root.val); //不同于 preOrder 这里先右 root = root.right; } if(!s.isEmpty()){ root = s.pop(); root = root.left; } } Collections.reverse(res); return res;//翻转再返回结果就好 }
法2:双栈法,其实就是利用两个栈构造出 root-right-left 的序列,然后出栈即为反转了,不如上一个快
给两个栈 s1,s2;
将root入s1;
若s1非空,将s1的栈顶元素赋给root,然后将root入s2;然后先将root左结点入s1,后将root右结点入s1,顺序不能错;
若s2非空,出s2,就获得后序遍历;

public List<Integer> postorderTraversal(TreeNode root) { LinkedList<Integer> res = new LinkedList<Integer>(); //两个 stack ,一个 stack按跟左右进栈 //放到另一个stack,形成 跟 右左,然后出栈即得到结果 LinkedList<TreeNode> s1 = new LinkedList<TreeNode>(); LinkedList<TreeNode> s2 = new LinkedList<TreeNode>(); if(root == null) return res; s1.push(root); while(!s1.isEmpty()){ root = s1.pop(); s2.push(root); if(root.left != null) s1.push(root.left); if(root.right!= null) s1.push(root.right); } while(!s2.isEmpty()) res.add(s2.pop().val); return res; }
法3:直接法:
root表示下一个要处理的结点,初始化为根结点,pre 表示上一次刚刚输出过的结点
pre 的作用是对有孩子的结点进行判断,由于算法特性,每次到某个结点时,其左子树都输出过了
此时只要判断 pre 是否是当前结点的右孩子,如果不是,那么说明其右子树没走过,那么 root = 当前结点的右子树
否则就是刚刚输出了当前结点的右孩子,由于是后序,其右子树也必定都输出过了,此时只要输出当前结点,更新 pre 就好了。

public List<Integer> postorderTraversal(TreeNode root) { LinkedList<Integer> res = new LinkedList<Integer>(); LinkedList<TreeNode> s = new LinkedList<TreeNode>(); if(root == null) return res; TreeNode pre = null; //指向上一个访问的节点 while(root != null || !s.isEmpty()){ while(root != null){ s.push(root); root = root.left; } if(!s.isEmpty()){ // 此时栈顶元素的左子树都输出了 //左右子树都输出了,当前节点也可以输出 if(s.peek().right == null || s.peek().right == pre){ res.add(s.peek().val); pre = s.pop(); }else{//右子树存在,且没有入栈 root = s.peek().right; } } } return res; }
9.根据先序序列、中序序列来建立二叉树
直接递归即可,注意下标的范围要计算好

public TreeNode build(int[] pre, int plo, int phi, int[]in, int ilo, int ihi){ if(plo > phi) return null; TreeNode root = new TreeNode(pre[plo]); int pos = ilo;//不用 binary search,因为不移动有序 while(pos <= ihi){ if(in[pos] == root.val) break; else pos ++; } //这里的下标要严格注意 root.left = build(pre, plo+1, plo+(pos-ilo), in, ilo, pos-1); root.right= build(pre, phi-(ihi-pos-1), phi, in, pos+1, ihi); return root; }
10. 有序链表转化为平衡二叉树
类似于上边的递归,注意判别条件,左子树 cur == head 说明左边已经遍历完

public TreeNode sortedListToBST(ListNode head) { if(head == null) return null; ListNode runner = head;// runner 为快指针 ListNode walker = head;// walker 为慢指针 ListNode pre = null; // pre 为 walker 的前一个 //快慢指针法的判别条件 while(runner.next!=null&&runner.next.next!=null){ runner = runner.next.next; pre = walker; walker = walker.next; } if(pre != null ) pre.next = null; //切断链表 TreeNode root = new TreeNode(walker.val); //pre == head 意味着 左子树的最后一个节点以建好,不用在建 if(walker != head) root.left = sortedListToBST(head); root.right = sortedListToBST(walker.next);//第一句判定错误 return root; }
11.统计完全树节点的个数
简单的方法是直接统计左右子树高度,若相等,则说明以该节点为跟的子树是满二叉树,直接返回 2h-1 即可,若不是,则返回 count(左子树)+count(右子树)+1:

public int countNodes(TreeNode root) { if(root == null) return 0; TreeNode p = root, q = root; int hl = 0, hr = 0; while(p != null){//统计左子树高 p = p.left; hl ++; } while(q != null){//统计右子树高 q = q.right; hr ++; } if(hl == hr) return (1 << hl) - 1;// <=> 2^hl - 1 return 1 + countNodes(root.left) + countNodes(root.right); }
12.递归建立 BST

static TreeNode root = null; public static void build(int[] nums) { for(int i=0; i<nums.length; ++ i) { build(root, nums[i]); } } public static void build(TreeNode node, int n) { if (root == null) { root = new TreeNode(n); return; } if (n < node.val) { //落在左子树 if (node.left == null) node.left = new TreeNode(n); else build(node.left, n); } else { // 落在右子树 if (node.right == null) node.right = new TreeNode(n); else build(node.right, n); } }
13.判断一棵树是否是另一颗树的子结构

public boolean HasSubtree(TreeNode root1, TreeNode root2) { if (root1 == null || root2 == null) return false; return judge(root1, root2) || judge(root1.left, root2) || judge(root1.right, root2); } public boolean judge(TreeNode root1, TreeNode root2) { if (root2 == null) return true; if (root1 == null) return false; return root1.val == root2.val && judge(root1.left, root2.left) && judge(root1.right, root2.right); }
14. 将 BST 每个节点的值替换为比其大的值的和。

// 将二叉查找树替换为其中序遍历后边节点的和 int sum = 0; public void Rep(TreeNode root) { if(root == null) return; Rep(root.right); sum += root.val; root.val = sum - root.val; Rep(root.left ); }