树可以说是面试官最喜欢考的类型之一了,因为树作为基础数据结构的同时,实用性也很强,工作中树的数据结构也很常用,并且我们可以看到,树的大多数题难度也比较适中,大多是些中等难度的题,简直为了面试而生。当然后面还有面试官最爱的类型,即动态规划和回溯等。其实树的类型题,只要掌握两种模板方法,大部分的题都可以迎刃而解了,一种是递归法,一种是迭代法,因为树的题,大多都是在深搜或广搜的遍历过程中解决的。
树是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由 n(n > 0) 个有限节点组成一个具有层次关系的集合。

把它叫做「树」是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。树有以下几个特点:
接下来介绍几种常见的树类型。
树主要有两种遍历方式,这两种遍历是图论中最基本的两种遍历方式:
遍历的表达法有4种方法:前序遍历、中序遍历、后序遍历、层次遍历。
例如下图:

前序遍历结果为:ABDECF(根-左-右)
中序遍历结果为:DBEAFC(左-根-右)(仅二叉树有中序遍历)
后序遍历结果为:DEBFCA(左-右-根)
层次遍历结果为:ABCDEF(同广度优先搜索)
那么遍历方式与表达法对应为:
做树的遍历时,深度优先遍历的递归法,当然是指用递归实现,迭代法则是用栈实现,因为我们知道,栈其实就是递归的一种是实现结构。而广度优先遍历的迭代法,则用是队列实现。
首先是树的代码实现,其实这就是leetcode上的树实现:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
大家会发现二叉树的代码和链表是差不多的,相对于链表,二叉树的节点里多了一个指针,有两个指针,指向左右孩子。刷leetcode题时,节点的定义默认都定义好了,但面试是不会给你定义的,要自己练到会。
首先我们先讲解用递归法写前中后序深搜代码,写所有的递归代码时都要注意以下两点:
前序遍历递归法
class Solution {
public List preorderTraversal(TreeNode root) {
List result = new ArrayList();
preorder(root, result);
return result;
}
public void preorder(TreeNode root, List result) {
if (root == null) {
return;
}
result.add(root.val);
preorder(root.left, result);
preorder(root.right, result);
}
}
中序遍历递归法
class Solution {
public List inorderTraversal(TreeNode root) {
List res = new ArrayList<>();
inorder(root, res);
return res;
}
void inorder(TreeNode root, List list) {
if (root == null) {
return;
}
inorder(root.left, list);
list.add(root.val); // 注意这一句
inorder(root.right, list);
}
}
后序遍历递归法
class Solution {
public List postorderTraversal(TreeNode root) {
List res = new ArrayList<>();
postorder(root, res);
return res;
}
void postorder(TreeNode root, List list) {
if (root == null) {
return;
}
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val); // 注意这一句
}
}
迭代法代码实现
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。接下来我们就用栈来实现迭代法代码。
前序遍历迭代法
/**
* 中-左-右,入栈顺序:中-右-左
*/
class Solution {
public List preorderTraversal(TreeNode root) {
List result = new ArrayList<>();
if (root == null){
return result;
}
Stack stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null){
stack.push(node.right);
}
if (node.left != null){
stack.push(node.left);
}
}
return result;
}
}
中序遍历迭代法
/**
* 顺序: 左-中-右 入栈顺序: 左-右
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List result = new ArrayList<>();
if (root == null){
return result;
}
Stack stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()){
if (cur != null){
stack.push(cur);
cur = cur.left;
}else{
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
后序遍历迭代法
// 顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public List postorderTraversal(TreeNode root) {
List result = new ArrayList<>();
if (root == null){
return result;
}
Stack stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node = stack.pop();
result.add(node.val);
if (node.left != null){
stack.push(node.left);
}
if (node.right != null){
stack.push(node.right);
}
}
Collections.reverse(result);
return result;
}
}
统一迭代法
迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。那有没有统一的迭代方式呢?有的, 这种方法也可以叫做标记法。
前序遍历统一迭代法
class Solution {
public List preorderTraversal(TreeNode root) {
List result = new LinkedList<>();
Stack st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
中序遍历统一迭代法
class Solution {
public List inorderTraversal(TreeNode root) {
List result = new LinkedList<>();
Stack st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
后序遍历统一迭代法
class Solution {
public List postorderTraversal(TreeNode root) {
List result = new LinkedList<>();
Stack st = new Stack<>();
if (root != null) st.push(root);
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
st.push(node); // 添加中节点
st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
} else { // 只有遇到空节点的时候,才将下一个节点放进结果集
st.pop(); // 将空节点弹出
node = st.peek(); // 重新取出栈中元素
st.pop();
result.add(node.val); // 加入到结果集
}
}
return result;
}
}
上面的遍历的代码内容很多,但都是讲的深度优先搜索,接下来讲的是广度优先搜索,即层序遍历。队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出,则是为了模拟深度优先遍历也就是递归的逻辑。
层序遍历递归法
class Solution {
public List> resList = new ArrayList>();
public List> levelOrder(TreeNode root) {
checkFun(root,0);
return resList;
}
//DFS--递归方式
public void checkFun(TreeNode node, Integer deep) {
if (node == null) return;
deep++;
if (resList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List item = new ArrayList();
resList.add(item);
}
resList.get(deep - 1).add(node.val);
checkFun(node.left, deep);
checkFun(node.right, deep);
}
}
层序遍历迭代法
class Solution {
public List> resList = new ArrayList>();
public List> levelOrder(TreeNode root) {
checkFun(root);
return resList;
}
//BFS--迭代方式--借助队列
public void checkFun(TreeNode node) {
if (node == null) return;
Queue que = new LinkedList();
que.offer(node);
while (!que.isEmpty()) {
List itemList = new ArrayList();
int len = que.size();
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) que.offer(tmpNode.left);
if (tmpNode.right != null) que.offer(tmpNode.right);
len--;
}
resList.add(itemList);
}
}
}

题目解析:迭代法,用指针和栈,按左中右的顺序遍历该数组。其实前中后序都可用栈实现迭代法,后序可以由前序反转所得,中序可由指针判断和栈所得。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 迭代法
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List res = new ArrayList<>();
Stack stack = new Stack<>();
if (root == null) {
return res;
}
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
// 判断有没有到最左子树
if (cur != null) {
stack.push(cur); // 左
cur = cur.left;
} else {
TreeNode temp = stack.pop();
res.add(temp.val); // 中
cur = temp.right; // 右
}
}
return res;
}
}

题目解析:递归,需注意,当前值要大于所有左子树的所有值,小于右子树的所有值。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public boolean isValidBST(TreeNode root) {
return helper(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean helper(TreeNode node, long min, long max) {
if (node == null) {
return true;
}
if (node.val <= min || node.val >= max) {
return false;
}
return helper(node.left, min, node.val) && helper(node.right, node.val, max);
}
}

题目解析:中序遍历过程中,记录错误两个错误排序节点,最后进行交换,只需要中序遍历一遍就可以了。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 中序遍历
*/
class Solution {
TreeNode t1, t2, pre;
public void recoverTree(TreeNode root) {
helper(root);
int temp = t1.val;
t1.val = t2.val;
t2.val = temp;
}
public void helper(TreeNode root){
if (root == null) return ;
helper(root.left);
if (pre != null && pre.val > root.val) {
if (t1 == null) t1 = pre;
t2 = root;
}
pre = root;
helper(root.right);
}
}

题目解析:直接中序遍历两颗树,只要一个值不一样,即返回。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null && q==null){
return true;
}
if(p!=null && q!=null && p.val==q.val ){
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}else {
return false;
}
}
}

题目解析:递归解决,如果root为空,则为对称,为递归出口。否则比较,左子树左边树和右子树右边树 and 左子树右边树和右子树左边树 是否相等。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
// 为空直接返回
if (root == null) {
return true;
}
boolean res = helper(root.left, root.right);
return res;
}
public boolean helper(TreeNode leftTree, TreeNode rightTree) {
// 左右子树同时为空,则不对称
if (leftTree == null && rightTree == null) {
return true;
}
// 一支子树为空,另一支不为空,则不对称
if (leftTree == null || rightTree == null || leftTree.val != rightTree.val) {
return false;
}
// 分别判断:左子树左边树和右子树右边树 and 左子树右边树和右子树左边树 是否相等
boolean res = helper(leftTree.left, rightTree.right) && helper(leftTree.right, rightTree.left);
return res;
}
}

题目解析:广度优先,用递归把每层值都扫完即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public List> levelOrder(TreeNode root) {
return helper(new ArrayList<>(),root,0);
}
List> helper(List> res,TreeNode root,int deep){
if(root!=null){
if(res.size()<=deep){
res.add(new ArrayList());
}
List list = res.get(deep);
list.add(root.val);
helper(res,root.left,deep+1);
helper(res,root.right,deep+1);
}
return res;
}
}

题目解析:广度优先,对应层需要判断一下奇偶,决定是头插还是尾插即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public List> zigzagLevelOrder(TreeNode root) {
List> res = new ArrayList<>();
helper(res, root, 0);
return res;
}
private void helper(List> res, TreeNode root, int deep) {
if (root == null) {
return;
}
if (res.size() == deep) {
res.add(new ArrayList());
}
if ((deep & 1) == 1){
res.get(deep).add(0, root.val);
} else {
res.get(deep).add(root.val);
}
helper(res, root.left, deep + 1);
helper(res, root.right, deep + 1);
}
}

题目解析:直接一手递归,返回左右子树的高度,再做对比即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
}

题目解析:先序遍历的第一个数为root,在中序中确定,中序左边为左子树,右边为右子树,再递归。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
private Map indexMap;
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if(preorder_left > preorder_right)
return null;
//前序遍历的第一个节点为根节点
int preoder_root = preorder_left;
//在中序遍历中定位根节点
int inorder_root = indexMap.get(preorder[preoder_root]);
//先把根节点建立出来
TreeNode root = new TreeNode(preorder[preoder_root]);
//获取左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
//递归构造左子树,并连接到根节点
root.left = myBuildTree(preorder, inorder, preoder_root + 1, preoder_root + size_left_subtree, inorder_left, inorder_root - 1);
root.right = myBuildTree(preorder, inorder, preoder_root + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
indexMap = new HashMap<>();
//因为前序遍历和中序遍历序列长度一致所以只需一个HashMap
for(int i=0; i 
题目解析:通过后序最后一位肯定根,然后在中序中分为左右两颗树,再后序中找到两颗树的后序排列方式,最后递归。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
int post_idx;
int[] postorder;
int[] inorder;
Map idx_map = new HashMap();
public TreeNode helper(int in_left, int in_right) {
if (in_left > in_right) {
return null;
}
int root_val = postorder[post_idx];
TreeNode root = new TreeNode(root_val);
int index = idx_map.get(root_val);
post_idx--;
root.right = helper(index + 1, in_right);
root.left = helper(in_left, index - 1);
return root;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
this.postorder = postorder;
this.inorder = inorder;
post_idx = postorder.length - 1;
int idx = 0;
for (Integer val : inorder) {
idx_map.put(val, idx++);
}
return helper(0, inorder.length - 1);
}
}

题目解析:广度优先搜索,用队列头插法存值。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 广度优先搜索
*/
class Solution {
public List> levelOrderBottom(TreeNode root) {
LinkedList> res = new LinkedList<>();
if (root == null)
return res;
Queue queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
List oneLevel = new ArrayList<>();
// 每次都取出一层的所有数据
int count = queue.size();
for (int i = 0; i < count; i++) {
TreeNode node = queue.poll();
oneLevel.add(node.val);
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
// 每次都往队头塞
res.addFirst(oneLevel);
}
return res;
}
}

题目解析:该数组可看做树的中序排列,每次找中间数为根,左右两边为左右子树,当然只有中序排序,结果不唯一。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
// 左右等分建立左右子树,中间节点作为子树根节点,递归该过程
return nums == null ? null : buildTree(nums, 0, nums.length - 1);
}
private TreeNode buildTree(int[] nums, int l, int r) {
if (l > r) {
return null;
}
// 求中点不要用 int mid = (l + r)/2,有溢出风险
int m = l + (r - l) / 2;
TreeNode root = new TreeNode(nums[m]);
root.left = buildTree(nums, l, m - 1);
root.right = buildTree(nums, m + 1, r);
return root;
}
}

题目解析:快慢指针,递归。慢指针每进一步,快指针进两步,找到中点即根。
代码如下:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public TreeNode sortedListToBST(ListNode head) {
// 快慢指针找到链表的中点,中点作为根结点,两边作为左右子树
if(head == null) return null;
if(head.next == null) return new TreeNode(head.val);
// 快慢指针找中间结点
ListNode fast = head, slow = head, pre = null;
while(fast != null && fast.next != null){
fast = fast.next.next;
pre = slow;
slow = slow.next;
}
// 分割出左链表,用于构造本结点的左子树
pre.next = null;
// 分割出右链表,用于构造本结点的右子树
ListNode rightList = slow.next;
// 用中间结点构造根结点
TreeNode root = new TreeNode(slow.val);
// 构造左子树
root.left = sortedListToBST(head);
// 构造右子树
root.right = sortedListToBST(rightList);
// 返回本结点所在子树
return root;
}
}

题目解析:递归查找左右子树,然后判断左右子树的高度差,是否绝对值超过1。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public boolean isBalanced(TreeNode root) {
return getDepth(root) != -1;
}
// 返回二叉树的深度,如果该二叉树不是平衡二叉树则返回-1
public int getDepth(TreeNode root) {
if (root == null) return 0;
int leftHeight = getDepth(root.left);
int rightHeight = getDepth(root.right);
if (leftHeight == -1 || rightHeight == -1) return -1; // 左右子树出现了非平衡二叉树,直接返回
return Math.abs(leftHeight - rightHeight) > 1 ? -1 : Math.max(leftHeight, rightHeight) + 1;
}
}

题目解析:做广度优先搜索,找到最小值即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
private boolean hasFound = false;
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Queue queue = new LinkedList<>();
Queue levels = new LinkedList<>();
queue.add(root);
levels.add(1);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
int level = levels.poll();
if (isLeaf(node)) {
return level;
}
if (node.left != null) {
if (isLeaf(node.left)) {
return level + 1;
}
queue.add(node.left);
levels.add(level + 1);
}
if (node.right != null) {
if (isLeaf(node.right)) {
return level + 1;
}
queue.add(node.right);
levels.add(level + 1);
}
}
return Integer.MAX_VALUE;
}
private boolean isLeaf(TreeNode node) {
return node.left == null && node.right == null;
}
}

题目解析:递归的同时,查找路径之和。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return targetSum - root.val == 0;
}
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
}
}

题目解析:虽然是道树的题,但却是用经典回溯模板去解题。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 回溯
*/
class Solution {
List> res = new ArrayList<>();
public List> pathSum(TreeNode root, int sum) {
List cur = new ArrayList<>();
helper(root, cur, 0, sum);
return res;
}
public void helper(TreeNode node, List cur, int sum, int target){
// 1
if(node == null){
return ;
}
if(node.left == null && node.right == null && node.val + sum == target){
cur.add(node.val);
res.add(new ArrayList<>(cur));
cur.remove(cur.size() - 1);
return ;
}
// 2
// 2.1
cur.add(node.val);
// 2.2
helper(node.left, cur, sum + node.val, target);
helper(node.right, cur, sum + node.val, target);
// 2.3
cur.remove(cur.size() - 1);
}
}

题目解析:递归,从最左子树开始,将左树放在根的右树指向上,左树置空,原右树放在现右树的最右分树上。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public void flatten(TreeNode root) {
if(root == null) return;
flatten(root.left);
flatten(root.right);
TreeNode tmp = root.right;
root.right = root.left;
root.left = null;
while(root.right != null) root = root.right;
root.right = tmp;
}
}

题目解析:递归,root的连接已做好,左右子树可能借来用,这样只用常量额外空间即可完成题目,root.left.next = root.right; root.right.next = root.next.left;。
代码如下:
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
/**
* 递归
*/
class Solution {
public Node connect(Node root) {
if (root == null) {
return null;
}
if (root.left != null) {
// 连接左右子树
root.left.next = root.right;
if (root.next != null) {
// 右子树的next,可以通过root的连接找到
root.right.next = root.next.left;
}
}
connect(root.left);
connect(root.right);
return root;
}
}

题目解析:遍历,把每一层存在的所有点,用虚拟头节点串起来即可。
代码如下:
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
/**
* 遍历
*/
class Solution {
public Node connect(Node root) {
if (root == null) {
return root;
}
Node cur = root;
while (cur != null) {
// 用一个头节点标注下一层的链表开始
Node head = new Node(0);
// 新建虚拟头节点
Node pre = head;
// 用当前层去更改下一层
while (cur != null) {
// 只要左、右子节点存在,则用头节点串起来
if (cur.left != null) {
head.next = cur.left;
head = head.next;
}
if (cur.right != null) {
head.next = cur.right;
head = head.next;
}
cur = cur.next;
}
// 移动到下一层,这里 pre.next(head.next) 为下一层第一个节点
cur = pre.next;
}
return root;
}
}

题目解析:直接递归,深度优先搜索即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 深度优先搜索
*/
class Solution {
public int sumNumbers(TreeNode root) {
return helper(root, 0);
}
public int helper(TreeNode root, int temp) {
int res = 0;
if (root == null) {
return temp;
}
temp = temp * 10 + root.val;
// 如果左子树不为空,递归出左子树的值
if (root.left != null) {
res += helper(root.left, temp);
}
// 如果右子树不为空,递归出右子树的值
if (root.right != null) {
res += helper(root.right, temp);
}
// 如果左、右子树为空,返回本叶子节点值
if (root.left == null && root.right == null) {
res = temp;
}
return res;
}
}

题目解析:用哈希表做记忆化搜索优化,避免重复的查找。
代码如下:
/**
* 记忆化搜索
*/
class Solution {
public List wordBreak(String s, List wordDict) {
Map>> map = new HashMap>>();
List> wordBreaks = backtrack(s, s.length(), new HashSet(wordDict), 0, map);
List breakList = new LinkedList();
for (List wordBreak : wordBreaks) {
breakList.add(String.join(" ", wordBreak));
}
return breakList;
}
public List> backtrack(String s, int length, Set wordSet, int index, Map>> map) {
if (!map.containsKey(index)) {
List> wordBreaks = new LinkedList>();
if (index == length) {
wordBreaks.add(new LinkedList());
}
for (int i = index + 1; i <= length; i++) {
String word = s.substring(index, i);
if (wordSet.contains(word)) {
List> nextWordBreaks = backtrack(s, length, wordSet, i, map);
for (List nextWordBreak : nextWordBreaks) {
LinkedList wordBreak = new LinkedList(nextWordBreak);
wordBreak.offerFirst(word);
wordBreaks.add(wordBreak);
}
}
}
map.put(index, wordBreaks);
}
return map.get(index);
}
}

题目解析:迭代法,用栈来代替递归(递归本质上,也需要用栈存储局部变量值)把值按中右左的顺序依次存入栈,而后每次循环时依次弹出,为什么要先压入右子树值再压入左子树值呢,因为,先入栈的后出栈,这样可以先存右子树的值,前中后序都可用栈实现迭代法,后序可以由前序反转所得,中序可由指针判断和栈所得。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 迭代法
*/
class Solution {
public List preorderTraversal(TreeNode root) {
List res = new ArrayList<>();
Stack stack = new Stack<>();
if (root == null) {
return res;
}
// 首先把根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 前序遍历,先把当前节点加入集合
res.add(temp.val); // 中
// 如果右节点不为空则把其加入栈
if(temp.right != null) stack.push(temp.right); // 右
// 如果左节点不为空则把其加入栈
if(temp.left != null) stack.push(temp.left); // 左
}
return res;
}
}

题目解析:迭代法,用栈来代替递归(递归本质上,也需要用栈存储局部变量值),把值按中左右的顺序依次存入栈。然后结果为中右左的"前序"遍历,再反转集合,即得到左右中的后序遍历集合。前中后序都可用栈实现迭代法,后序可以由前序反转所得,中序可由指针判断和栈所得。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 迭代法
*/
class Solution {
public List postorderTraversal(TreeNode root) {
List res = new ArrayList<>();
Stack stack = new Stack<>();
if (root == null) {
return res;
}
// 首先把根节点压入栈
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 后序遍历,先压根节点(之后会反转)
res.add(temp.val);
// 如果左节点不为空则把其加入栈
if (temp.left != null) stack.push(temp.left); // 左 先压入左子树节点,这样后出栈,进而组成 中右左 组合
// 如果左节点不为空则把其加入栈
if (temp.right != null) stack.push(temp.right); // 右
}
Collections.reverse(res);
return res;
}
}

题目解析:按层遍历,每次添加最右边的元素,即为我们所需的元素。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 按层遍历
*/
class Solution {
public List rightSideView(TreeNode root) {
List res = new ArrayList<>();
if(root == null) return res;
Queue queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
int count = queue.size();
while(count > 0){
count--;
TreeNode cur = queue.poll();
if(count == 0){
res.add(cur.val);
}
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right != null){
queue.add(cur.right);
}
}
}
return res;
}
}

题目解析:这也是一道回溯模板题,之后会详细讲解该模板,这里有所了解即可。
代码如下:
/**
* 回溯 + 字典树
*/
class Solution {
int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public List findWords(char[][] board, String[] words) {
Trie trie = new Trie();
for (String word : words) {
trie.insert(word);
}
Set ans = new HashSet();
for (int i = 0; i < board.length; ++i) {
for (int j = 0; j < board[0].length; ++j) {
dfs(board, trie, i, j, ans);
}
}
return new ArrayList(ans);
}
public void dfs(char[][] board, Trie now, int i1, int j1, Set ans) {
if (!now.children.containsKey(board[i1][j1])) {
return;
}
char ch = board[i1][j1];
now = now.children.get(ch);
if (!"".equals(now.word)) {
ans.add(now.word);
}
board[i1][j1] = '#';
for (int[] dir : dirs) {
int i2 = i1 + dir[0], j2 = j1 + dir[1];
if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
dfs(board, now, i2, j2, ans);
}
}
board[i1][j1] = ch;
}
}
class Trie {
String word;
Map children;
boolean isWord;
public Trie() {
this.word = "";
this.children = new HashMap();
}
public void insert(String word) {
Trie cur = this;
for (int i = 0; i < word.length(); ++i) {
char c = word.charAt(i);
if (!cur.children.containsKey(c)) {
cur.children.put(c, new Trie());
}
cur = cur.children.get(c);
}
cur.word = word;
}
}

题目解析:针对完全二叉树的技巧解法,满二叉树的结点数为:2^depth - 1,每颗完全二叉树,分成左右子树,最终都可以分成小的满二叉树,然后小的二叉满树,再用公式计算。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 树
*/
class Solution {
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {
// 左子树是满二叉树
// 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
return (1 << leftDepth) + countNodes(root.right);
} else {
// 右子树是满二叉树
return (1 << rightDepth) + countNodes(root.left);
}
}
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
}
return depth;
}
}

题目解析:递归,递归从最底层交换到上层。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
return helper(root);
}
public TreeNode helper(TreeNode root) {
if (root == null) {
return root;
}
TreeNode temp = helper(root.left);
root.left = helper(root.right);
root.right = temp;
return root;
}
}

题目解析:需要自行建立二叉树数据结构,用于存储子树的结点数量。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 二叉树
*/
class Solution {
public int kthSmallest(TreeNode root, int k) {
MyBst bst = new MyBst(root);
return bst.kthSmallest(k);
}
}
class MyBst {
TreeNode root;
Map nodeNum;
public MyBst(TreeNode root) {
this.root = root;
this.nodeNum = new HashMap();
countNodeNum(root);
}
// 返回二叉搜索树中第k小的元素
public int kthSmallest(int k) {
TreeNode node = root;
while (node != null) {
int left = getNodeNum(node.left);
if (left < k - 1) {
node = node.right;
k -= left + 1;
} else if (left == k - 1) {
break;
} else {
node = node.left;
}
}
return node.val;
}
// 统计以node为根结点的子树的结点数
private int countNodeNum(TreeNode node) {
if (node == null) {
return 0;
}
nodeNum.put(node, 1 + countNodeNum(node.left) + countNodeNum(node.right));
return nodeNum.get(node);
}
// 获取以node为根结点的子树的结点数
private int getNodeNum(TreeNode node) {
return nodeNum.getOrDefault(node, 0);
}
}

题目解析:根据二搜索树的特点,如果p.val < root.val < q.val,则 root 为p,q的最近公共祖先。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
* 树
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root.val == p.val) {
return p;
}
if (root.val == q.val) {
return q;
}
// 如果p.val,q.val都小于root.val,则说明两值都在root的左子树中
if (p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q);
}
// 如果p.val,q.val都大于root.val,则说明两值都在root的右子树中
if (p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
}

题目解析:递归,root为空,p,q为root时 直接返回root,递归左右,都不为空说明一个在左,一个在右,此时返回root。此时谁不为空返回谁。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
* 递归
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 找到叶子节点或找到其中一个节点,即可返回
if (root == null || p == root || q == root) 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;
}
}

题目解析:一手递归,直接梭哈,再打印出路径即可。
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* 递归
*/
class Solution {
public List binaryTreePaths(TreeNode root) {
List res = new ArrayList<>();
helper(res, root, "");
return res;
}
public void helper(List res, TreeNode root, String p){
if(root != null){
StringBuilder str = new StringBuilder(p);
str.append(root.val);
if(root.left == null && root.right == null){
res.add(str.toString());
return;
}
else{
str.append("->");
String sb = str.toString();
helper(res,root.left,sb);
helper(res,root.right,sb);
}
}
}
}