二叉树的遍历

递归:

void traverse (TreeNode root) {
if (root == null) {
return null;
}
//前序遍历位置
traverse(root.left);
//中序遍历位置
traverse(root.right);
//后序遍历位置
}

144. 二叉树的前序遍历

前序非递归:

public static List<Integer> preOrder(TreeNode root) {
if (root == null) {
return null;
} List<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root); while (!stack.isEmpty()) {
// 先遍历根结点
TreeNode node = stack.pop();
res.add(node.val); // 打印顺序为:根 左 右,因此先将右子结点入栈
if (node.right != null) {
stack.push(node.right);
} if (node.left != null) {
stack.push(node.left);
} } return res;
}

94. 二叉树的中序遍历

中序非递归:

 public static List<Integer> infixOrder(TreeNode root) {
if (root == null) {
return null;
} List<Integer> res = new LinkedList<>();
TreeNode temp = root;
Deque<TreeNode> stack = new LinkedList<>();
while (temp != null || !stack.isEmpty()) {
while (temp != null) {
stack.push(temp); // 加入栈
temp = temp.left; // 到最左边结点停止
} temp = stack.pop(); // 访问栈顶元素
res.add(temp.val); temp = temp.right; //下一个遍历的元素是temp的右子树的最左边结点
} return res;
}

145. 二叉树的后序遍历

后序非递归:

// 后序可参照前序:后序为:左右根,我们只需按照:根右左遍历然后翻转即可
public static List<Integer> postOrder(TreeNode root) {
if (root == null) {
return null;
} LinkedList<Integer> res = new LinkedList<>();
Deque<TreeNode> stack = new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode temp = stack.pop();
// 每次添加到头,最后输出的结果自然为:根右左的逆序
res.addFirst(temp.val); if (temp.left != null) {
stack.push(temp.left);
} if (temp.right != null) {
stack.push(temp.right);
}
} return res;
}

注意:如果非递归解法难以理解,可以先按照上面的代码结合案例手推一下。重要的还是要先形成模板并记忆,间隔着多做几次也就慢慢理解了。

102. 二叉树的层序遍历

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

/**
* 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<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
}
// 创建队列并加入头结点
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root); while (!queue.isEmpty()) {
// 获取当前层的结点个数
int size = queue.size();
List<Integer> rowList = new LinkedList<>(); // 将当前层结点按照先进先出(从左至右)的方式出队,同时将非空子结点加入队尾
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
rowList.add(node.val); if (node.left != null) {
queue.offer(node.left);
} if (node.right != null) {
queue.offer(node.right);
}
} res.add(rowList);
} return res;
}
}

104. 二叉树的最大深度

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

lass Solution {
/**
* 定义:
* 返回以root为根结点的最大深度
*/
public int maxDepth(TreeNode root) {
//base case, root为空说明树的高度为0,退出递归
if (root == null) {
return 0;
} /**
* 根据定义,就根节点来说,树的最大深度为:
* 左右子树的最大深度中的最大值 + 1
*/
int left = maxDepth(root.left);
int right = maxDepth(root.right); return 1 + Math.max(left, right);
}
}
  • 二叉树相关的很多题目都是由二叉树的三种递归遍历演化而来
  • 此题其实就是二叉树的后序遍历演化而来,要知道当前二叉树的最大深度自然要先知道两棵子树的最大深度,因此用后序遍历(自底向上)
  • 编写递归程序切记不要用脑袋模拟递归栈,函数定义好后,根据定义编写代码即可

110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

class Solution {
//先假定是平衡二叉树
private boolean balance = true;
public boolean isBalanced(TreeNode root) {
height(root);
return balance;
} //后序遍历而来,自底向上获取两棵子树的高度,并检查节点左右子树高度只差是否小于等于1
private int height(TreeNode root) {
if (root == null) {
return 0;
} int left = height(root.left);
int right = height(root.right); if (Math.abs(left - right) > 1) {
balance = false;
} return Math.max(left, right) + 1;
}
}

124. 二叉树中的最大路径和

路径 被定义为一条从树中任意节点出发,沿父节点 - 子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 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 maxPathSum(TreeNode root) {
maxSumToDescendant(root);
return maxPathSum;
} //先将 树的最大路径和 初始化为最小值
private int maxPathSum = Integer.MIN_VALUE; // 函数定义:当前结点到 子孙(不一定包含叶子结点) 结点的最大路径和(最少为其自身一个结点)
private int maxSumToDescendant(TreeNode root) {
if (root == null) {
return 0;
} // 小于0则认为对最大路径和没有贡献
int left = Math.max(0, maxSumToDescendant(root.left));
int right = Math.max(0, maxSumToDescendant(root.right)); // 自底向上返回的过程中顺带计算 树的最大路径和(当前结点到左边子孙结点的最大路径和 + 当前结点 + 当前结点到右边子孙结点的最大路径和)
maxPathSum = Math.max(maxPathSum, left + root.val + right); return root.val + Math.max(left, right);
}
}

236. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

思路:后序遍历演变而来,若找到其中一个结点就自底向上返回。若p、q互不为对方子树中的结点,p、q最终会在某个结点相遇,该节点就为最近公共祖先;否则p或q即为最近公共结点。

class Solution {
//重要已知:p != q
// p 和 q 均存在于给定的二叉树中。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || 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 ? left : right;
}
}

107. 二叉树的层序遍历 II

给定一个二叉树,返回其节点值自底向上的层序遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
} Deque<TreeNode> queue = new LinkedList<>();
queue.offerLast(root); while (!queue.isEmpty()) {
//获取当前层的结点数量
int size = queue.size(); //暂存当前层的所有结点
List<Integer> tempList = new LinkedList<>();
TreeNode tempNode;
for (int i = 0; i < size; i++) {
tempNode = queue.poll();
tempList.add(tempNode.val); if (tempNode.left != null) {
queue.offer(tempNode.left);
} if (tempNode.right != null) {
queue.offer(tempNode.right);
}
} //将每一层的结果 逆序 放入最终的list中
res.addFirst(tempList);
}
return res;
}
}

103. 二叉树的锯齿形层序遍历

给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
} Deque<TreeNode> queue = new LinkedList<>();
queue.offerLast(root);
boolean flag = true; while(!queue.isEmpty()) {
//获取当前层的结点数量
int size = queue.size(); LinkedList<Integer> tempList = new LinkedList<>();
TreeNode tempNode;
for (int i = 0; i < size; i++) {
tempNode = queue.pollFirst(); if (flag == true) {
//从前往后,顺序存放
tempList.addLast(tempNode.val);
} else {
//从前往后,逆序存放
tempList.addFirst(tempNode.val);
} if (tempNode.left != null) {
queue.offerLast(tempNode.left);
} if (tempNode.right != null) {
queue.offerLast(tempNode.right);
}
} res.add(tempList);
//每遍历完一层切换flag
flag = !flag;
}
return res;
}
}

二叉搜索树

98. 验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。
/**
* 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 isValidBST(root, null, null);
} //定义:当前结点为根结点的二叉树是否为二叉搜索树。二叉搜索树的每个结点都有一个上下界(除了根节点)
private boolean isValidBST(TreeNode node, TreeNode low, TreeNode high) {
//base case
if (node == null) {
return true;
} //base case,当前结点小于等于下界或大于等于上界都不满足二叉搜索树
if (low != null && node.val <= low.val) return false;
if (high != null && node.val >= high.val) return false; boolean ret = isValidBST(node.left, low, node) && isValidBST(node.right, node, high);
return ret;
}
}

推荐题解:验证二叉搜索树(BST:给子树上所有节点都加一个边界)

701. 二叉搜索树中的插入操作

给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果

class Solution {
// 定义:将当前值插入以当前结点为根的二叉搜索树并返回根节点
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
} // 一定要根据定义来编写递归代码
if (root.val > val) {
// val 小于当前结点值则插入左子树
root.left = insertIntoBST(root.left, val);
} else {
root.right = insertIntoBST(root.right, val);
} return root;
}
}

450. 删除二叉搜索树中的节点

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

说明: 要求算法时间复杂度为 O(h),h 为树的高度。

class Solution {
// 定义:删除当前结点为根结点的二叉搜索树中值为 key 的结点
public TreeNode deleteNode(TreeNode root, int key) {
// base case
if (root == null) {
return root;
} // 切记根据定义编写递归代码
if (root.val == key) {
//base case, 删除结点是叶子结点或只是有一个子树的非叶结点
if (root.left == null) return root.right;
if (root.right == null) return root.left; // 有两个子树的非叶子结点,用右子树的最小结点替换当前结点,然后删除右子树最小结点
TreeNode node = getMin(root.right);
root.val = node.val; root.right = deleteNode(root.right, node.val); // key 大于当前结点值 则根据定义在右子树中删除
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else if (root.val > key){
root.left = deleteNode(root.left, key);
} return root;
} // 获取root为根的子树的最小结点(最左边结点)
private TreeNode getMin(TreeNode root) {
while(root.left != null) {
root = root.left;
} return root;
}
}

LeetCode入门指南 之 二叉树的更多相关文章

  1. LeetCode入门指南 之 链表

    83. 删除排序链表中的重复元素 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 .返回同样按升序排列的结果链表. class Soluti ...

  2. LeetCode入门指南 之 排序

    912. 排序数组 给你一个整数数组 nums,请你将该数组升序排列. 归并排序 public class Sort { //归并排序 public static int[] MergeSort(in ...

  3. LeetCode入门指南 之 栈和队列

    栈 155. 最小栈 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈. push(x) -- 将元素 x 推入栈中. pop() -- 删除栈顶的元素. top( ...

  4. LeetCode入门指南 之 回溯思想

    模板 result = {} void backtrack(选择列表, 路径) { if (满足结束条件) { result.add(路径) return } for 选择 in 选择列表 { 做选择 ...

  5. LeetCode入门指南 之 动态规划思想

    推荐学习labuladong大佬的动态规划系列文章:先弄明白什么是动态规划即可,不必一次看完.接着尝试自己做,没有思路了再回过头看相应的文章. 动态规划一般可以由 递归 + 备忘录 一步步转换而来,不 ...

  6. LeetCode入门指南 之 二分搜索

    上图表示常用的二分查找模板: 第一种是最基础的,查找区间左右都为闭区间,比较后若不等,剩余区间都不会再包含mid:一般在不需要确定目标值的边界时,用此法即可. 第二种查找区间为左闭右开,要确定targ ...

  7. Web API 入门指南 - 闲话安全

    Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患.相关的防御技巧以及Web AP ...

  8. Vue.js 入门指南之“前传”(含sublime text 3 配置)

    题记:关注Vue.js 很久了,但就是没有动手写过一行代码,今天准备入手,却发现自己比菜鸟还菜,于是四方寻找大牛指点,才终于找到了入门的“入门”,就算是“入门指南”的“前传”吧.此文献给跟我一样“白痴 ...

  9. yii2实战教程之新手入门指南-简单博客管理系统

    作者:白狼 出处:http://www.manks.top/document/easy_blog_manage_system.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文 ...

随机推荐

  1. python基础之os模块操作

    # os模块 目录相关内置库import os# . 当前目录 .. 返回上一级目录# 1. os.path.abspath() --获取当前文件的绝对路径(不包含os模块.py) pwd# path ...

  2. Python自动化测试面试题-Selenium篇

    目录 Python自动化测试面试题-经验篇 Python自动化测试面试题-用例设计篇 Python自动化测试面试题-Linux篇 Python自动化测试面试题-MySQL篇 Python自动化测试面试 ...

  3. U盘启动盘安装win10出现cdboot:couldn't find ntldr

    格式化硬盘后出现:cdboot:couldn't find ntldr  解决方法: 分区时格式选择:硬盘格式导致的,一般出现在win10装win7时,需要用pe系统里的分区工具重新给为硬盘分区,并将 ...

  4. 第三十二篇 -- CreateFile、ReadFile、WriteFile

    一.CreateFile 这是一个多功能的函数,可打开或创建文件或者I/O设备,并返回可访问的句柄:控制台,通信资源,目录(只读打开),磁盘驱动器,文件,邮槽,管道. 函数原型: HANDLE WIN ...

  5. Java的equsals和==

    先上结论:在我们常用的类中equals被重写后,作用就是为了比较对象的内容,==是比较对象的内存地址.但并不能说所有的equals方法就是比较对象的内容. Java 中的==: 1.对于对象引用类型: ...

  6. Mybatis学习笔记-缓存

    简介 什么是缓存 **将一次查询的结果暂存至内存,后续查询只需查询缓存** 为什么使用缓存 **减少与数据库的交互次数,减少系统开销,提高系统效率** 什么样的数据能使用缓存 **经常查询且不常修改的 ...

  7. 四、从GitHub浏览Prism示例代码的方式入门WPF下的Prism之Mvvm的13示例

    上一篇之分析了示例,没有最终写DEMO,把这一篇分析完,总结后一起写Prism下的MVVM例子. 这一篇开始分析从13示例开始,分析到MVVM主要部分结束然后写一个分析后的总结DEMO 添加一段新的内 ...

  8. 获取浏览器中url的参数

    例如: 浏览器的地址是:http://localhost:8080/src/views/moneyDetail?id=10 vue 获取浏览器的参数 获取id的参数:this.$route.query ...

  9. 如何在Spring Data MongoDB 中保存和查询动态字段

    原文: https://stackoverflow.com/questions/46466562/how-to-save-and-query-dynamic-fields-in-spring-data ...

  10. docker搭建clickhouse集群

    //需要先搭建zookeeper集群.机器1: sudo docker run -d \ --name clickhouse --ulimit nofile=262144:262144 \ -p 81 ...