考察的知识点主要在于树的数据结构(BST,AVL)、遍历方式(前序,中序,后序,层次)、遍历算法(DFS,BFS,回溯)以及遍历时借助的数据结构如队列和栈。由于树本身就是一个递归定义的结构,所以在递归求解问题时,要善于将问题转化成合适的子问题,思考清楚子问题的形式、递归的出口、父问题与子问题的联系。

以下这些问题难度都不太大,很多都是一次过,比上次链表专题的思维难度小多了。

难度 题目 知识点
04. 重建二叉树 依据前序和中序遍历重建 递归
★★ 17. 树的子结构 递归
18. 二叉树的镜像 简单递归
22. 从上往下打印二叉树 bfs Queue的使用
23. 判断是否为二叉搜索树的后序遍历 BST 递归
24. 二叉树中和为某一值的路径 dfs 回溯 Collections.sort()
★★ 26. 二叉搜索树与双向链表 递归 中序遍历
38. 二叉树的深度 递归
39. 平衡二叉树 平衡二叉树 递归
57. 二叉树的下一个结点 中序遍历 循环 回溯后的情况
58. 对称的二叉树 递归 可转化为非递归
59. 按之字形顺序打印二叉树 Stack 层次遍历变形
60. 把二叉树打印成多行 Queue LinkedList 层次遍历
★★ 61. 序列化二叉树 递归 string.split() string.equals
62. 二叉搜索树的第K个结点 BST 中序遍历 递归

04. 重建二叉树

依据前序和中序遍历重建 递归

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

递归建树。注意递归出口,当有 0 或一个节点时直接返回。

/**
* Definition for binary tree
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
if(pre.size()==0)
return NULL;
if(pre.size()==1)
return new TreeNode(pre[0]);
TreeNode* root = new TreeNode(pre[0]);
int lenl =0, lenr = 0,len = vin.size();
while(lenl<len && vin[lenl]!= pre[0])
lenl++;
vector<int> npre,nvin; npre = vector<int>(pre.begin()+1,pre.begin()+1+lenl);
nvin = vector<int>(vin.begin(),vin.begin()+lenl);
root->left = reConstructBinaryTree(npre,nvin); npre = vector<int>(pre.begin()+1+lenl,pre.end());
nvin = vector<int>(vin.begin()+lenl+1,vin.end());
root->right = reConstructBinaryTree(npre,nvin); return root;
}
};

17. 树的子结构 ++

递归

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

/**
A: 两个步骤:1.【包不包括】判断顶点值是否相同
2.【是不是】顶点值的情况下A与B能不能对应上(注意A可能是大于B的
*/
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root2 == null || root1 == null)
return false; if(root1.val == root2.val && IsSubtree(root1,root2))
return true;
else
return HasSubtree(root1.left,root2)
|| HasSubtree(root1.right,root2);
} private boolean IsSubtree(TreeNode root1,TreeNode root2){
if(root2 == null)return true;
if(root1 == null)return false; if(root1.val != root2.val) return false; return IsSubtree(root1.left,root2.left)
&& IsSubtree(root1.right,root2.right);
}
}

18. 二叉树的镜像

简单递归

操作给定的二叉树,将其变换为源二叉树的镜像。

二叉树的镜像定义:源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
public class Solution {
public void Mirror(TreeNode root) {
if(root == null) return; TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp; Mirror(root.left);
Mirror(root.right);
}
}

22. 从上往下打印二叉树

bfs Queue的使用

Queue的使用:

Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。

offer,add 比较:

共同之处是建议实现类禁止添加 null 元素,否则会报空指针 NullPointerException;

不同之处在于 add() 方法在添加失败(比如队列已满)时会报一些运行时错误;而 offer() 方法即使在添加失败时也不会崩溃,只会返回 false。

poll,remove 比较:

remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似,但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。

peek,element 比较:

element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

Java Code

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
public ArrayList PrintFromTopToBottom(TreeNode root) {
ArrayList ans = new ArrayList();
Queue queue = new LinkedList();
if (root == null) return ans;
queue.add(root);
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
ans.add(poll.val);
if (poll.left != null)
queue.offer(poll.left);
if (poll.right != null)
queue.offer(poll.right);
}
return ans;
}
}

23. 判断是否为二叉搜索树的后序遍历

BST 递归

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

递归思路:

已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。

  1. 确定root;
  2. 遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
  3. 遍历右子树,若发现有小于root的值,则直接返回false;
  4. 分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。
Java Code

public class Solution {
public boolean VerifySquenceOfBST(int[] sequence) {
if (sequence == null || sequence.length == 0)
return false;
return check(sequence, 0, sequence.length - 1);
}
// [ , ]
private boolean check(int[] tree, int start, int end) {
if (start >= end)
return true;
int root = tree[end];
int mid = start;
while (mid

24. 二叉树中和为某一值的路径+

dfs 回溯 Collections.sort()

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

题目思路

深搜,搜到叶子时若和为target便将路径加入路径列表。路径列表排序后即为答案。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; public class Solution {
ArrayList<Integer> path = new ArrayList<>();
ArrayList<ArrayList<Integer>> pathList = new ArrayList<>(); public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
dfs(root, target);
Collections.sort(pathList, new Comparator<ArrayList<Integer>>() {
@Override
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
if (o1.size() >= o2.size())
return -1;
else
return 1;
}
});
return pathList;
} public void dfs(TreeNode root, int target) {
if (root == null) return;
path.add(root.val); if (target == root.val && root.left == null && root.right == null)
pathList.add((ArrayList<Integer>) path.clone());
else {
dfs(root.left, target - root.val);
dfs(root.right, target - root.val);
} path.remove(path.size() - 1); } }

26. 二叉搜索树与双向链表++

递归 中序遍历

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题思路

方法 1:先拍平左子树,再拍平右子树。左、中、右连接。【递归实现】

方法 2:【更简化】中序遍历,用一个 pLast 记录总的链表的末尾 ☆

Java代码

// 方法 1 代码
// 不能用 javax 的 Pair 所以写了个类中类
// java 的统一引用真是好哇
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null)
return pRootOfTree; return getFlat(pRootOfTree).min;
} public Pair getFlat (TreeNode root){
if(root.left==null && root.right==null)
return new Pair(root,root); TreeNode left,right,minn=root,maxx=root;
Pair tmp;
if(root.left!=null) {
tmp = getFlat(root.left);
minn = tmp.min;
left = tmp.max;
left.right=root;
root.left=left;
}
if(root.right!=null){
tmp=getFlat(root.right);
right = tmp.min;
maxx = tmp.max;
right.left=root;
root.right=right;
}
return new Pair(minn,maxx);
}
private class Pair{
TreeNode min,max;
Pair(TreeNode a,TreeNode b){
this.min=a;
this.max=b;
}
}
}
// ---------------------方法 2-----------------------------
private TreeNode pLast = null;
public TreeNode Convert(TreeNode root) {
if (root == null)
return null; // 如果左子树为空,那么根节点root为双向链表的头节点
TreeNode head = Convert(root.left);
if (head == null)
head = root; // 连接当前节点root和当前链表的尾节点pLast
root.left = pLast;
if (pLast != null)
pLast.right = root;
pLast = root; Convert(root.right); return head;
}

38. 二叉树的深度+

递归

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

相当简洁的写法!

public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
return Math.max(TreeDepth(root.left)+1,TreeDepth(root.right)+1);
}
}

39. 平衡二叉树

平衡二叉树 递归

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

平衡二叉树

平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树AVL替罪羊树Treap伸展树等。 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。——百度百科

Java Code

public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(Balance_depth(root)==-1)
return false;
else
return true;
} private int Balance_depth(TreeNode root){
if(root == null )
return 0; int dLeft=Balance_depth(root.left);
if(dLeft==-1) return -1;
int dRight=Balance_depth(root.right); if(dRight == -1||Math.abs(dLeft-dRight)>1)
return -1;
return Math.max(dLeft,dRight)+1;
}
}

57. 二叉树中序遍历的下一个结点+

中序遍历 循环 回溯后的情况

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解题思路

对于一个节点,

  • 如果有右子树,返回右子树最左下的节点;
  • 如果没有右子树,从此节点开始,返回第一个作为左儿子的节点的父亲,没有符合条件的就返回null。

Java Code

public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode) {
TreeLinkNode ans = null;
if (pNode == null) return ans; TreeLinkNode p = pNode;
if (p.right != null) {// 有右子树
ans = p.right;
while (ans.left != null)
ans = ans.left;
} else {
ans = p;
while (ans != null && ans.next != null && ans == ans.next.right)
ans = ans.next;
if (ans != null) {
ans = ans.next;
} }
return ans;
}
}

58. 对称的二叉树

递归 可转化为非递归

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot == null) return true;
return check(pRoot.left, pRoot.right);
} boolean check(TreeNode rt1, TreeNode rt2) {
if (rt1 == null && rt2 == null) return true;
else if (rt1 == null || rt2 == null) {
return false;
} else {
return rt1.val == rt2.val &&
check(rt1.left, rt2.right) && check(rt1.right, rt2.left);
}
}
}

59. 按之字形顺序打印二叉树

Stack 层次遍历变形

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

用两个栈交互倒,元素在两个栈的左右儿子压站顺序稍有不同。

Stack 的 peek() 只取第一个元素,删除用 pop()。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack; public class Solution {
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
if(pRoot==null) return ans; Stack<TreeNode> s1=new Stack<>();
Stack<TreeNode> s2=new Stack<>(); s1.push(pRoot);
ArrayList<Integer> curList;
TreeNode cur;
while(!s1.empty()||!s2.empty()){
curList = new ArrayList<>();
while (!s1.empty()){
cur = s1.peek();
s1.pop();
curList.add(cur.val);
if(cur.left!=null)s2.push(cur.left);
if(cur.right!=null)s2.push(cur.right);
}
ans.add(curList);
curList = new ArrayList<>();
while(!s2.empty()){
cur = s2.peek();
s2.pop();
curList.add(cur.val);
if(cur.right!=null) s1.push(cur.right);
if(cur.left!=null)s1.push(cur.left);
}
if(curList.size()>0) ans.add(curList);
} return ans; } }

60. 把二叉树打印成多行

Queue LinkedList 层次遍历

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路和上题相似,轮流使用两个队列避免用变量区别层数。

Java Code

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Solution {
public ArrayList> Print(TreeNode pRoot) {
ArrayList> ans = new ArrayList();
if(pRoot==null) return ans;
Queueq1=new LinkedList();
Queueq2=new LinkedList();
TreeNode cur;
ArrayList curList;
q1.offer(pRoot);
while (!q1.isEmpty()){
curList = new ArrayList();
while(!q1.isEmpty()){
cur = q1.peek();
q1.poll();
curList.add(cur.val);
if (cur.left!=null)q2.offer(cur.left);
if(cur.right!=null)q2.offer(cur.right);
}
ans.add(curList);
curList = new ArrayList();
while (!q2.isEmpty()){
cur=q2.peek();
q2.poll();
curList.add(cur.val);
if (cur.left!=null)q1.offer(cur.left);
if(cur.right!=null)q1.offer(cur.right);
}
if(curList.size()>0)ans.add(curList);
}
return ans;
}
}

# 61. 序列化二叉树++

递归 str.split() str.equals

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

递归实现。其中,反序列化需要借助一个变量记录反序列化的进展。

注意字符串的比较要用equals()。

import java.io.Serializable;
import java.util.ArrayList; public class Solution {
String Serialize(TreeNode root) {
if (root == null) return "";
StringBuilder ans = new StringBuilder();
Serialize2(root, ans);
return ans.toString();
} private void Serialize2(TreeNode rt, StringBuilder ans) {
if (rt == null) ans.append("#!");
else {
ans.append(Integer.toString(rt.val));
ans.append('!');
Serialize2(rt.left, ans);
Serialize2(rt.right, ans);
}
} private int idx=0;
TreeNode Deserialize(String str) {
if (str == null||str.equals("")) return null; TreeNode rt = null;
String[] nodes;
nodes = str.split("!");
idx = 0;
return Deserilize2(nodes);
} private TreeNode Deserilize2(String[] nodes) {
if (nodes[idx].equals("#")) {
idx++;
return null;
}
TreeNode rt = new TreeNode(new Integer(nodes[idx]));
idx++;
rt.left = Deserilize2(nodes);
rt.right = Deserilize2(nodes);
return rt;
}
}

62. 二叉搜索树的第K个结点

BST 中序遍历 递归

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

BST中序遍历的第K个就是第K小。

public class Solution {

    private int no=0;	// 记录已经中序遍历了几个元素。
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot==null||k==0) return null; TreeNode tmp=KthNode(pRoot.left,k);
if(tmp!=null) return tmp; // 左子树搜寻结果
if((++no)==k) return pRoot; // 自己是不是
return KthNode(pRoot.right,k); // 右子树搜寻结果
} }

《剑指offer》树专题 (牛客10.25)的更多相关文章

  1. 《剑指offer》Q13-18 (牛客10.13)

    目录 Q13 调整数组顺序使奇数位于偶数前 Q14 链表中倒数第k个结点 Q15 反转链表 Q16 合并两个有序链表 Q17 树的子结构 Q18 二叉树的镜像 Q13 调整数组顺序使奇数位于偶数前 输 ...

  2. 《剑指offer》Q01-12 (牛客10.11)

    目录 T1 二维部分有序数组查找 ☆ T2 字符串字符不等长替换 - 从后往前 T3 返回链表的反序 vector T4 重建二叉树 T5 两个栈模拟队列 T6 旋转数组中的最小元素 - 二分或暴力 ...

  3. 剑指offer——树的子结构 (JAVA代码)

    版权声明:本文为博主原创文章,未经博主允许不得转载. 题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构). 解题思路: 首先看牛客网给出的测试用例: ...

  4. 用js刷剑指offer(树的子结构)

    题目描述 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) 牛客网链接 js代码 /* function TreeNode(x) { this.val = x ...

  5. 《剑指offer》数组专题 (牛客10.22)

    目录 // Q01 二维部分有序数组查找 [善用性质] // Q06 旋转数组中的最小元素 [二分 || 暴力] Q13 调整数组顺序使奇数位于偶数前 / Q19 顺时针打印矩阵 [使用边界变量] / ...

  6. 《剑指offer》链表专题 (牛客10.23)

    难度 题目 知识点 03. 返回链表的反序 vector 递归,C++ STL reverse() * 14. 链表中倒数第k个结点 指针操作 15. 反转链表 头插法,递归 16. 合并两个有序链表 ...

  7. 剑指Offer 树的子结构

    题目描述 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构)     思路: 分为2个部分.1先找出A中和B根节点相同的节点r. 2,咱判断B中所有孩子节点是不 ...

  8. 剑指Offer——树的子结构

    题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) 分析: 先匹配到A的某个结点和B的根相同,然后往下继续匹配.不匹配则递归匹配左右子树. 代码: ...

  9. 剑指 offer 树的子结构

    题目描述: 输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构). 第一遍没写出来错误点:认为首先应该找到pRoot1等于pRoot2的节点,但是递归就是自己在不 ...

随机推荐

  1. 你的VCL界面开发不知所措?这款工具绝对超出预料

    DevExpress VCL Controls是 Devexpress公司旗下最老牌的用户界面套包.所包含的控件有:数据录入,图表,数据分析,导航,布局,网格,日程管理,样式,打印和工作流等,让您快速 ...

  2. java后台表单验证工具类

    /** * 描述 java后台表单验证工具类 * * @ClassName ValidationUtil * @Author wzf * @DATE 2018/10/27 15:21 * @VerSi ...

  3. app连接线上数据库进行本地接口测试

    1.将开发环境下数据库配置改为生产环境下的数据库连接 2.备份生产环境下的数据库数据以及结构,使用Postman请求开发(本地)环境下的接口 3.打开手机上安装的线上app改动接口时查看app是否发生 ...

  4. 浏览器事件环(EventLoop)

    1. 基础知识 1. js语言特点 1. js语言是单线程语言,主线程是单线程.如UI渲染,脚本加载是主线程任务. 2. js语言采用事件循环(EventLoop)机制. 2. 同步任务: 不被引擎挂 ...

  5. ES6-12.Symbol

    Symbol是ES6新增的原始类型数据,引入的初衷是为了对象可以有永不重复的属性名. 所以属性名可以是字符串外,还可以是Symbol值: const a = Symbol("a") ...

  6. Selenium结合BeautifulSoup4编写简单爬虫

    在学会了抓包,接口请求(如requests库)和Selenium的一些操作方法后,基本上就可以编写爬虫,爬取绝大多数网站的内容. 在爬虫领域,Selenium永远是最后一道防线.从本质上来说,访问网页 ...

  7. 主机,路由器,应用程序,sockets api的关系

  8. ie和vuex的兼容

    vuex requires a Promise polyfill in this browser. 在ie中的报错 需要安卓babel-polyfill,  然后在webpack.base.confi ...

  9. java并发编程--第一章并发编程的挑战

    一.java并发编程的挑战 并发编程需要注意的问题: 并发编程的目的是让程序运行的更快,然而并不是启动更多的线程就能让程序最大限度的并发执行.若希望通过多线程并发让程序执行的更快,会受到如下问题的挑战 ...

  10. 解决JAVA单步调试键盘输入被JDB占用的问题

    解决JAVA单步调试键盘输入被JDB占用的问题 问题来源: 在完成本周任务时,编写的代码中含有Scanner类,编译及运行过程均正确,但使用JDB单步调试时,运行到输入行无法在JDB内部输入变量值. ...