二叉树
方法论
二叉树的遍历方式是递归,写递归函数的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要跳入递归的细节。换句话说,先搞清楚当前 root 节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。把题目的要求细化成每个节点(或每几个节点)需要做的事情。
如,计算一棵二叉树共有几个节点:
// 定义:count(root) 返回以 root 为根的树有多少节点
int count(TreeNode root) {
// base case
if (root == null) return 0;
// 自己加上子树的节点数就是整棵树的节点数
return 1 + count(root.left) + count(root.right);
}
root 本身就是一个节点,加上左右子树的节点数就是以 root 为根的树的节点总数。左右子树的节点数怎么算?其实就是计算根为 root.left 和 root.right 两棵树的节点数,按照定义,递归调用 count 函数即可算出来。
框架:
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
// 前序遍历位置,在此处访问root.val
traverse(root.left)
// 中序遍历位置,在此处访问root.val
traverse(root.right)
// 后序遍历位置,在此处访问root.val
}
226-翻转二叉树
题目:输入一个二叉树根节点 root,把整棵树镜像翻转。
Invert a binary tree
思路:只要把二叉树上的每一个节点的左右子节点进行交换,最后的结果就是完全翻转之后的二叉树,如图所示。

// 将整棵树的节点翻转
TreeNode invertTree(TreeNode root) {
// 结束递归的条件
if (root == null) {
return null;
}
/**** 前序遍历位置 ****/
// root 节点需要交换它的左右子节点
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
// 让左右子节点继续翻转它们的子节点
invertTree(root.left);
invertTree(root.right);
return root;//注意return的是根节点
}
116-填充每个节点的下一个右侧节点指针
题目

难点在于如何连接不同子树上的节点,如节点5、6。如果递归函数的参数只有1个(传入1个节点),节点5、6是无法被连接的,因为每次递归,递归函数的作用域(所能访问到的节点)为当前节点、当前节点的左节点、当前节点的右节点,形成一个三角形作用域,节点5、6处于不同作用域中(即处于不同的三角形中),无法同时被访问到,故无法进行连接。
那我们就把递归函数的参数设置为2个(传入2个节点),每次递归,递归函数的作用域(所能访问到的节点)形成了一个梯形,包含6个节点,即参数节点1、参数节点1的左节点、参数节点1的右节点、参数节点2、参数节点2的左节点、参数节点2的右节点。这样节点5、6就可以被同时访问到了,可以进行连接了。
代码如下,示意图如下。
//错误代码,递归函数的参数只有1个
Node connect(Node root) {
if (root == null || root.left == null) {
return root;
}
root.left.next = root.right;
connect(root.left);
connect(root.right);
return root;
}
//正确代码,递归函数的参数有2个
//主函数
public Node connect(Node root) {
if(root == null){
return null;
}
connect2Nodes(root.left,root.right);
return root;
}
//辅助函数
public void connect2Nodes(Node n1,Node n2){
//n1在左,n2在右(不要反了)
//结束递归的条件,注意:这是一棵完美二叉树
if(n1 == null || n2 == null){//由于是完美二叉树,n1、n2实际上会同时为null,或同时不为null
return;
}
/**** 前序遍历位置 ****/
// 将传入的两个节点连接
n1.next = n2;
// 连接相同父节点的两个子节点
connect2Nodes(n1.left,n1.right);
connect2Nodes(n2.left,n2.right);
// 连接跨越父节点的两个子节点
connect2Nodes(n1.right,n2.left);
}

114-将二叉树展开为链表
题目
关键点是要“脑补”出二叉树被拉平(flatten)的过程,过程如下图所示,蓝色框表示当前步骤所关注的二叉树部分,先关注root的左子树,将其拉平,然后关注root的右子树,将其拉平(本来就是平的),然后关注整体,将整体拉平。“拉平”具体又是怎么实现呢?以root的左子树为例,将根节点的左节点插入到根节点与右节点之间;以整体为例,将根节点(flatten后)的左子树,插入到根节点与(flatten后)的右子树之间。所谓的“拉平”就是我们要写的递归方法。

public void flatten(TreeNode root) {
if(root == null){
return;
}
//先拉平左子树
flatten(root.left);
//再拉平右子树
flatten(root.right);
/**** 后序遍历位置 ****/
//将拉平后的左子树插入到根节点与右子树之间
TreeNode rootLeft = root.left;
TreeNode rootRight = root.right;
if(rootLeft != null){
root.left = null;
root.right = rootLeft;
//将右子树拼接到原左子树的最右下末端(原左子树已经被flatten过了,一定是\形状)
TreeNode connectNode = rootLeft;
while(connectNode.right != null){
connectNode = connectNode.right;
}
//connectNode为原左子树的最右下末端节点
connectNode.right = rootRight;
}
}
654-构造最大二叉树
题目
思路:先要找到根节点root,即数组中的最大值,然后将数组中最大值左边的数构建成最大二叉树,作为根节点的左子树,然后将数组中最大值右边的数构建成最大二叉树,作为根节点的右子树。
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums,0,nums.length-1);
}
public TreeNode build(int[] nums,int start,int end){//start,end表明使用数组哪一步分
if(end < start){
return null;
}
//找数组中的最大值及其索引,作为根节点
int max = nums[start];
int index = start;
for(int i=start+1;i<=end;i++){
if(nums[i] > max){
max = nums[i];
index = i;
}
}
TreeNode root = new TreeNode(max);
//将最大值左边部分构建成最大二叉树,作为根节点的左子树
root.left = build(nums,start,index-1);
//将最大值右边部分构建成最大二叉树,作为根节点的右子树
root.right = build(nums,index+1,end);
return root;
}
105-用前序/中序遍历结果还原二叉树
题目
首先看前序遍历和后序遍历的递归代码,代码的结构,决定了遍历结果(数组)的特点。
void traverse(TreeNode root) {
// 前序遍历位置,在此处访问root.val
traverse(root.left);
traverse(root.right);
}
void traverse(TreeNode root) {
traverse(root.left);
// 中序遍历位置,在此处访问root.val
traverse(root.right);
}
从代码结构就能看出:
前序遍历的结果(数组),根节点在数组的第一个位置,然后是左子树的节点们,然后是右子树的节点们;
中序遍历的结果(数组),先是左子树的节点们,然后是根节点,最后是右子树的节点们。
事实确实如此,如图所示:

知道了前序遍历的结果(数组)与中序遍历的结果(数组)各自的特点之后,我们如何构建出二叉树呢?
根据前序遍历数组、中序遍历数组,构建二叉树可以分为以下步骤:
1.先从数组中找出根节点的值,创建根节点
2.获得左子树的中序遍历数组、前序遍历数组,根据这两个数组构建二叉树作为根节点的左子树
3.获得右子树的中序遍历数组、前序遍历数组,根据这两个数组构建二叉树作为根节点的右子树
“根据前序遍历数组、中序遍历数组,构建二叉树”就是我们的递归函数的任务。
注意:当递归函数的参数为数组时,同时使用两个int参数指明当前数组的start index和end index,这样可以避免创建新的数组,减少开销。
第2步和第3步中,获取子树的前序遍历数组和中序遍历数组,最好画个图,方便理解:
我们要知道左子树在数组中的长度(左子树节点个数),才好推算出前序遍历中左子树在数组中的终止位置。

public TreeNode buildTree(int[] preorder, int[] inorder) {
return build(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
}
public TreeNode build(int[] preorder,int pStart,int pEnd, int[] inorder,int iStart,int iEnd){
if(pStart > pEnd || iStart > iEnd){
return null;
}
//1.先从数组中找出根节点的值,创建根节点
//前序遍历数组的第一个值就是根节点的值
int rootVal = preorder[pStart];
TreeNode root = new TreeNode(rootVal);
//获得根节点在中序遍历数组中的位置
int rootIndex4Inorder = 0;
for(int i=iStart;i<=iEnd;i++){
if(inorder[i] == rootVal){
rootIndex4Inorder = i;
break;
}
}
//得到左子树的长度
int leftTreeSize = rootIndex4Inorder - iStart;
//2.获得左子树的中序遍历数组、前序遍历数组,根据这两个数组构建二叉树作为根节点的左子树
root.left = build(preorder,pStart+1,pStart+leftTreeSize,inorder,iStart,rootIndex4Inorder-1);
//3.获得右子树的中序遍历数组、前序遍历数组,根据这两个数组构建二叉树作为根节点的右子树
root.right = build(preorder,pStart+leftTreeSize+1,pEnd,inorder,rootIndex4Inorder+1,iEnd);
return root;
}
106-通过后序和中序遍历结果构造二叉树
题目
和上个题类似。先看中序遍历、后序遍历的代码结构:
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
// 后序遍历位置,在此处访问root.val
}
void traverse(TreeNode root) {
traverse(root.left);
// 中序遍历位置,在此处访问root.val
inorder.add(root.val);
traverse(root.right);
}
由于这样的代码结构,导致了遍历结果的特点:
- 中序遍历数组:左子树-根节点-右子树
- 后序遍历数组:左子树-右子树-根节点
明确了两种遍历方式的数组特点之后,考虑如何根据前序遍历数组、中序遍历数组构建二叉树,可以分为以下步骤:
1.先从数组中找出根节点的值,创建根节点
2.获得左子树的中序遍历数组、后序遍历数组,根据这两个数组构建二叉树作为根节点的左子树
3.获得右子树的中序遍历数组、后序遍历数组,根据这两个数组构建二叉树作为根节点的右子树
“根据前序遍历数组、后序遍历数组,构建二叉树”就是我们的递归函数的任务。
第2步和第3步中,获取子树的后序遍历数组和中序遍历数组,最好画个图,方便理解:
我们要知道左子树在数组中的长度(左子树节点个数),才好推算出后序遍历中左子树在数组中的终止位置。

public TreeNode buildTree(int[] inorder, int[] postorder) {
return build(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
}
public TreeNode build(int[] inorder,int inStart,int inEnd,int[] postorder,int poStart,int poEnd){
if(inStart > inEnd || poStart > poEnd){
return null;
}
//1.先从数组中找出根节点的值,创建根节点
//后序遍历数组的最后一个值是根节点值
int rootVal = postorder[poEnd];
TreeNode root = new TreeNode(rootVal);
//获得根节点在中序遍历数组中的位置
int rootIndex4Inorder = 0;
for(int i=inStart;i<=inEnd;i++){
if(inorder[i] == rootVal){
rootIndex4Inorder = i;
break;
}
}
//得到左子树的长度
int leftTreeSize = rootIndex4Inorder - inStart;
//2.获得左子树的中序遍历数组、后序遍历数组,根据这两个数组构建二叉树作为根节点的左子树
root.left = build(inorder,inStart,rootIndex4Inorder-1,postorder,poStart,poStart+leftTreeSize-1);
//3.获得右子树的中序遍历数组、后序遍历数组,根据这两个数组构建二叉树作为根节点的右子树
root.right = build(inorder,rootIndex4Inorder+1,inEnd,postorder,poStart+leftTreeSize,poEnd-1);
return root;
}
652-寻找重复的子树
题目
思考以下问题:
1.怎么描述一棵二叉树的模样?
如果两棵二叉树的前序or中序or后序遍历结果相同,说明这两个二叉树一模一样。我们可以通过拼接字符串的方式把二叉树序列化,我们用非数字的特殊符 # 表示空指针,并且用字符 , 分隔每个二叉树节点值,这属于序列化二叉树的套路了。
2.怎么知道一个二叉树的模样?
知道它的左子树的模样、右子树的模样,再加上根节点,就知道该二叉树的模样了。换言之,知道它的左子树的遍历序列、右子树的遍历序列,再加上该根节点本身,拼接得到的序列就是该二叉树的模样。(对应后序遍历框架)
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
/* 判断以root为根节点的二叉树,是否和其他二叉树一样 */
}
3.怎么知道其他二叉树的模样?
想知道我本人跟其他人一样不一样,总得知道其他人的样子吧。所以要有一个数据结构来记录各二叉树的遍历序列,我们选择使用Map集合,序列作为键,出现次数作为值。给出的方法的返回类型为List< TreeNode >,结合题目要求,是用List集合存放发生重复的二叉树的根节点。
Map trees = new HashMap<String,Integer>();
List roots = new LinkedList<TreeNode>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
describe(root);
return roots;
}
public String describe(TreeNode root){
if(root == null){
return "#";
}
//得到root的左子树的遍历序列
String leftTree = describe(root.left);
//得到root的右子树的遍历序列
String rightTree = describe(root.right);
/**后序遍历位置:得到以root为根节点的二叉树的后序遍历序列(描述该二叉树的模样)**/
String description = leftTree + "," + rightTree + "," + root.val;
/**后序遍历位置:判断以root为根节点的二叉树,是否和其他二叉树一样**/
//将该序列存储到Map中
Object freq = trees.get(description);
if(freq == null){//Map中还没有该序列
trees.put(description,1);
}else{//Map中已经存在该序列了
Integer freq_ = (Integer)freq;
trees.put(description,freq_+1);
if(freq_ == 1){//第一次出现重复的时候,将根节点放入List
roots.add(root);
}
}
return description;
}
297-二叉树的序列化与反序列化
题目
规定的代码框架:
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
}
}
题目说序列化流程我们自己确定,所谓序列化,就是二叉树遍历,有4种方式:前序、中序、后序、层序。
字符串拼接是很浪费空间的,所以使用StringBuilder。
方法1:前序遍历
反序列化的时候会利用前序遍历序列的特点:根-左子树-右子树

public class Codec {
//分隔符
String SEP = ",";
//空节点
String NULL = "#";
/* 主函数,将二叉树序列化为字符串 */
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root,sb);
return sb.toString();
}
/* 辅助函数,将二叉树存入 StringBuilder */
public void serialize(TreeNode root,StringBuilder sb){
if(root == null){
sb.append(NULL).append(SEP);//在序列中加入#以表示为空节点
return;
}
/**前序遍历位置**/
sb.append(root.val).append(SEP);//在序列中加入本节点的值
serialize(root.left,sb);
serialize(root.right,sb);
}
/* 主函数,将字符串反序列化为二叉树结构 */
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
//将String转化为List,方便遍历各个值
LinkedList<String> vals = new LinkedList<>();
String[] strArray = data.split(SEP);//以SEP分隔符,获得String数组
for(String val:strArray){
vals.add(val);
}
return deserialize(vals);
}
/* 辅助函数,通过序列构造二叉树 */
public TreeNode deserialize(LinkedList<String> vals){//从前往后在list中取元素
if(vals.isEmpty()){
return null;
}
//任务:构造根节点,构造其左右子树并与根节点相连接
//前序遍历序列的第一个值是根节点
String first = vals.removeFirst();//removeFirst()是LinkedList的方法,移除并返回此列表的第一个元素
if(first.equals(NULL)){//如果是#
return null;
}
//生成根节点
TreeNode root = new TreeNode(Integer.parseInt(first));//String转Integer
//构建左子树二叉树,连接到root
root.left = deserialize(vals);
//构建右子树二叉树,连接到root
root.right = deserialize(vals);
return root;
}
}
方法2:后序遍历
反序列化的时候会利用后序遍历序列的特点:左子树-右子树-根

public class Codec {
//分隔符
String SEP = ",";
//空节点
String NULL = "#";
/* 主函数,将二叉树序列化为字符串 */
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root,sb);
return sb.toString();
}
/* 辅助函数,将二叉树存入 StringBuilder */
public void serialize(TreeNode root,StringBuilder sb){
if(root == null){
sb.append(NULL).append(SEP);//在序列中加入#以表示为空节点
return;
}
serialize(root.left,sb);
serialize(root.right,sb);
/**后序遍历位置**/
sb.append(root.val).append(SEP);//在序列中加入本节点的值
}
/* 主函数,将字符串反序列化为二叉树结构 */
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
//将String转化为List,方便遍历各个值
LinkedList<String> vals = new LinkedList<>();
String[] strArray = data.split(SEP);//以SEP分隔符,获得String数组
for(String val:strArray){
vals.add(val);
}
return deserialize(vals);
}
/* 辅助函数,通过序列构造二叉树 */
public TreeNode deserialize(LinkedList<String> vals){//从后往前在list中取元素
if(vals.isEmpty()){
return null;
}
//任务:构造根节点,构造其左右子树并与根节点相连接
//后序遍历序列的最后一个值是根节点
String first = vals.removeLast();//removeLast()是LinkedList的方法,移除并返回此列表的最后一个元素
if(first.equals(NULL)){//如果是#
return null;
}
//生成根节点
TreeNode root = new TreeNode(Integer.parseInt(first));//String转Integer
//注意:后序遍历序列特点为左子树-右子树-根,我们从后往前取元素,所以要先构建右子树
//先构建右子树二叉树,连接到root
root.right = deserialize(vals);
//再构建左子树二叉树,连接到root
root.left = deserialize(vals);
return root;
}
}
方法3:中序遍历(行不通)
可以用中序遍历进行序列化,但无法进行反序列化,因为找不到根节点。反序列化的时候,我们的任务是:先构造根节点,然后构造左右子树,第一步就是找根节点的值。对于前序遍历序列而言,根节点值就是左边第一个值;对于后序遍历序列而言,根节点值就是右边最后一个值;对于中序遍历序列而言,根节点值在中间,无法确定是哪个值。
方法4:层级遍历
队列Queue是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。
offer:从队列后端插入
poll:从队列前端取出
层级遍历二叉树的代码框架:
标准的二叉树层级遍历框架,从上到下,从左到右打印每一层二叉树节点的值,可以看到,队列 q 中不会存在 null 指针。
void traverse(TreeNode root) {
if (root == null) return;
// 初始化队列,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
/* 层级遍历代码位置 */
System.out.println(root.val);
/*****************/
if (cur.left != null) {
q.offer(cur.left);
}
if (cur.right != null) {
q.offer(cur.right);
}
}
}
结合自己的需求,把上边框架改一下,就能实现序列化。
反序列化,要看遍历序列的特点,如图所示,根据序列特点来写构建二叉树的流程。所谓“构建”就是“找到根节点,然后找到其左节点,与根节点连接,然后找到其右节点,与根节点连接”,不断重复这一步骤,就能从上到下、从左至右地还原二叉树。
设置一个指针来从左到右遍历数组,还需要一个队列来存放尚未有子节点的根节点。为何需要该队列?为了获得根节点,比如当指针走到下图中第一个#时,它并不知道自己的根节点是谁,需要有额外的数据结构来记录。节点2作为子节点时,节点2比节点3先被连接到节点1(因为指针从左往右遍历数组),节点2就会比节点3先进队列;节点2作为根节点时,节点2比节点3先出队列(因为队列是先进先出的),节点2比节点3先连接自己的子节点(因为指针从左往右遍历数组)。

public class Codec {
//分隔符
String SEP = ",";
//空节点
String NULL = "#";
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null){
return "";
}
//创建队列,存放层级遍历顺序的节点
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
StringBuilder sb = new StringBuilder();
while(!queue.isEmpty()){//当队列中存在节点时
//从队列中取节点
TreeNode cur = queue.poll();
/* 层级遍历代码位置 */
if(cur == null){
sb.append(NULL).append(SEP);
continue;//跳到下一次循环
}
sb.append(cur.val).append(SEP);
//往队列里存节点
queue.offer(cur.left);
queue.offer(cur.right);
}
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data.isEmpty()){
return null;
}
String[] array = data.split(SEP);
//创建根节点(层序遍历序列的第一个值是根节点值)
TreeNode root = new TreeNode(Integer.parseInt(array[0]));
//该队列用来存放尚未连接子节点的根节点
Queue<TreeNode> rootQ = new LinkedList<>();
rootQ.offer(root);
int index = 0;//指示进行到数组中哪个位置
while(index < array.length-1){//每次循环处理一对左右节点(2个节点),所以要-1
//队列中取出根节点
TreeNode parent = rootQ.poll();
//获取左节点的值
String leftNode = array[++index];
if(!leftNode.equals(NULL)){
//创建左节点,并连接到根节点
parent.left = new TreeNode(Integer.parseInt(leftNode));
//将该节点放进队列
rootQ.offer(parent.left);
}else{
parent.left = null;
}
//获取右节点的值
String rightNode = array[++index];
if(!rightNode.equals(NULL)){
//创建右节点,并连接到根节点
parent.right = new TreeNode(Integer.parseInt(rightNode));
//将该节点放进队列
rootQ.offer(parent.right);
}else{
parent.right = null;
}
}
return root;
}
}
222-完全二叉树(complete binary tree)的节点个数
常规解法
计算一棵二叉树的节点,这很简单,递归就行:
// 定义:count(root) 返回以 root 为根的树有多少节点
int count(TreeNode root) {
// base case
if (root == null) return 0;
// 自己加上子树的节点数就是整棵树的节点数
return 1 + count(root.left) + count(root.right);
}
N为节点总数,时间复杂度O(N)。怎么算的?
递归深度(即次数) * 每轮递归的时间复杂度=N * 1=N
最优解
但是题目说的是完全二叉树,如果利用完全二叉树的特性的话,可能会有时间复杂度更低的算法。
这个题目有如下关键点:
- 1.理解完全二叉树的定义,满二叉树的定义
- 2.知道满二叉树的节点计算,有现成的公式
英文:
完全二叉树-complete binary tree
满二叉树-perfect binary tree
完全二叉树的定义
题目说了:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h个节点。

满二叉树的定义
每层的节点都是满的,很好理解。
一个高度为h的满二叉树,节点数为:
1+2+2^2 +……+ 2^h
这不是个等比数列嘛,用等比数列的求和公式:

可得节点数N=2^h-1
注意:高度指的是边的个数,而不是节点个数,上图中满二叉树的高度为2而不是3。
所以我们现在知道了普通二叉树节点个数的计算方法为递归,时间复杂度O(N);满二叉树的节点个数的计算方法为直接套公式。显然满二叉树的计算过程简单太多了。我们就把两者结合一下,在递归过程中,如果该轮递归对应的二叉树是满二叉树,就直接套公式了。那么怎么判断一棵二叉树是否是满二叉树呢?如下图所示,左边线和右边线长度相同的话,就是满二叉树。

public int countNodes(TreeNode root) {
TreeNode leftTreeRoot = root;
TreeNode rightTreeRoot = root;
//得到左边线的长度(注意:高度是边的个数,不是节点个数)
int leftTreeHeight = 0;
while(leftTreeRoot != null){
leftTreeHeight++;
leftTreeRoot = leftTreeRoot.left;
}
//得到右边线的长度
int rightTreeHeight = 0;
while(rightTreeRoot != null){
rightTreeHeight++;
rightTreeRoot = rightTreeRoot.right;
}
if(leftTreeHeight == rightTreeHeight){//如果两边线的长度相同,说明是一棵满二叉树,可以直接使用公式
return (int)Math.pow(2,leftTreeHeight)-1;
}
//以当前root为根节点的树不是满二叉树时,继续递归
return 1+countNodes(root.left)+countNodes(root.right);
}
时间复杂度分析:

一棵完全二叉树的左右子树中,必定有一个满二叉树,满二叉树直接套公式返回值,另外一个子树使用递归。
每轮递归,while循环的次数就是这棵树的高度,计算如下
N=2^h-1
h=log(N+1)
则每轮递归的时间复杂度是O(logN)
算法的递归深度就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logN*logN)
这里理解的不是很透彻…
总结
想好这个递归函数的任务,及其参数,这个递归函数的作用就是:根据XXX参数完成XXX任务。不要跳入细节。
根据遍历序列还原二叉树的条件
如果序列中没有空节点的信息(如:1,2,4,3,),必须有前、中、后序遍历序列中的两种才能还原二叉树;
如果序列中有空节点的信息(如:1,2,#,4,#,#,3,#,#,),使用一种遍历顺序就能还原二叉树。
一些常用方法
Queue队列:
offer-从队列后端插入
poll-从队列前端取出
创建:Queue<> q = new LinkedList<>();
StringBuilder:
append-追加
LinkedList:
removeFirst-移走第一个值并返回其值
removeLast-移走最后一个值并返回其值
Math.pow(底数,幂) :求几次方
二叉树遍历的代码框架、序列特点
前序、中序、后序:
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
// 前序遍历位置,在此处访问root.val
traverse(root.left)
// 中序遍历位置,在此处访问root.val
traverse(root.right)
// 后序遍历位置,在此处访问root.val
}
层级:
void traverse(TreeNode root) {
if (root == null) return;
// 初始化队列,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
/* 层级遍历代码位置 */
System.out.println(root.val);
/*****************/
if (cur.left != null) {
q.offer(cur.left);
}
if (cur.right != null) {
q.offer(cur.right);
}
}
}
前序:根-左子树-右子树
中序:左子树-根-右子树
后序:左子树-右子树-根
层级:一层一层的
本文详细探讨了二叉树的各种遍历方法,包括前序、中序、后序以及层级遍历,并提供了相关算法题目的解题思路。通过递归思想解析了如何构造、翻转和连接二叉树节点,以及如何根据遍历序列还原二叉树。此外,还介绍了完全二叉树的节点计算优化方法,总结了二叉树遍历的代码框架和特点。

被折叠的 条评论
为什么被折叠?



