目录
一、重要基础概念
树的深度:相对概念。每一层的深度不同。根节点的深度为1.
树的高度:最大的深度是高度。
满二叉树:每一层都放满。层数为K,结点总数为2的k次方-1
完全二叉树:每一层从左到右依次存放。
二、二叉树的遍历
2.1 前、中、后序遍历(递归实现)
解题思路:
要点1:前、中、后序的遍历顺序相同,只是输出root的时机不同。
实现代码:
// 前序
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
result.add(root.val);
List<Integer> leftTree = preorderTraversal(root.left);
result.addAll(leftTree);
List<Integer> rightTree = preorderTraversal(root.right);
result.addAll(rightTree);
return result;
}
}
// 中序
class Solution {
private List<Integer> result = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null){
return result;
}
inorderTraversal(root.left);
result.add(root.val);
inorderTraversal(root.right);
return result;
}
}
// 后序
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
List<Integer> leftTree = postorderTraversal(root.left);
result.addAll(leftTree);
List<Integer> rightTree = postorderTraversal(root.right);
result.addAll(rightTree);
result.add(root.val);
return result;
}
}
2.2 前、中、后序遍历(非递归实现)
OJ链接:
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
Stack<TreeNode> stack = new Stack();
// 这个循环就是:按照中序遍历顺序,一直往下走,将所有遇到的不为null的节点记录下来
while(root != null || !stack.empty()){
while(root != null){
stack.push(root);
result.add(root.val);
root = root.left;
}
// 走到这,root为空。以示例一为例,证明走到了3.left这个节点
// 需要先将3这个节点从stack中拿出来,这样才能遍历他的右节点
TreeNode tmp = stack.pop();
root = tmp.right; // 现在到了3的右节点,假设他有,现在需要回到while
}
return result;
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack();
if(root == null) return result;
while(root != null || !stack.empty()){
while(root != null){
stack.push(root);
root = root.left;
}
TreeNode tmp = stack.pop();
result.add(tmp.val);
root = tmp.right;
}
return result;
}
}
class Solution {
public int pos;
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder == null || postorder == null){
return null;
}
pos = postorder.length-1;
return buildTree1(inorder,postorder,0,inorder.length-1);
}
public TreeNode buildTree1(int[] inorder, int[] postorder,int begin,int end){
if(begin > end || begin < 0){
return null;
}
int data = postorder[pos];
TreeNode root = new TreeNode(data);
int rootPos = findPos(inorder,data,begin,end);
pos--;
// 左右根 是从中序遍历的结果的最后一个节点往前找,因此,确定根节点后,应该先确定root.right
root.right = buildTree1(inorder,postorder,rootPos+1,end);
root.left = buildTree1(inorder,postorder,begin,rootPos-1);
return root;
}
public int findPos(int[] inorder,int data,int begin,int end){
for(int i=begin;i<=end;i++){
if(inorder[i] == data){
return i;
}
}
return -1;
}
}
2.3 二叉树的层序遍历
2.3.1 层序遍历代码
OJ链接:102. 二叉树的层序遍历 - 力扣(LeetCode)
解题思路:
要点1:定义一个队列实现层序遍历。队列中存放的是下一层的节点个数,队列的size就是下一层的节点数量。在遍历这一层的时候,将下一层的节点加入队列中
实现代码:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> queque = new LinkedList<>();
if(root == null){
return result;
}
queque.offer(root);
while(!queque.isEmpty()){
List<Integer> tmp = new ArrayList<>();
int size = queque.size();
// 第一层 size = 1 ,将root的左右节点加入
// 第二次 size = 2,while循环2次,将 root.left和root.right的左右节点加入
while(size > 0){
TreeNode top = queque.poll();
tmp.add(top.val);
if(top.left != null){
queque.offer(top.left);
}
if(top.right != null){
queque.offer(top.right);
}
size--;
}
result.add(tmp);
}
return result;
}
}
2.3.2 层序遍历变种OJ题目(左右视图)
剑指 Offer II 046. 二叉树的右侧视图 - 力扣(LeetCode)
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<List<Integer>> olderLevelRsult = orderLevel(root);
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
for(List<Integer> tmp : olderLevelRsult){
result.add(tmp.get(tmp.size()-1));
}
return result;
}
public List<List<Integer>> orderLevel(TreeNode root){
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root == null){
return result;
}
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> tmp = new ArrayList<>();
int size = queue.size();
while(size >0){
TreeNode top = queue.poll();
tmp.add(top.val);
if(top.left != null){
queue.offer(top.left);
}
if(top.right != null){
queue.offer(top.right);
}
size--;
}
result.add(tmp);
}
return result;
}
}
2.4 二叉树的相关性质求解
2.4.1 求二叉树的大小
解题思路:
遍历求解:挨个遍历,采用前序遍历的思路,如果root!=null,size++
子问题思路:左树+右树+1
实现代码:
// 子问题思路
// 获取总节点个数
public int getSize(TreeNode root){
if(root == null){
return 0;
}
int left = getSize(root.left);
int right = getSize(root.right);
return 1+left+right;
}
// 遍历思路
public int size = 0;
private void getSize2(TreeNode root){
if(root == null){
return;
}
size++;
getSize2(root.left);
getSize2(root.right);
}
2.4.2 求叶子节点的数量
解题思路:
要点1:叶子节点的判断条件:root.left==null&&root.right==null
实现代码:
// 获取叶子节点个数
public int num = 0;
public void getLeafSize1(TreeNode root){
if(root == null){
return;
}
if(root.left==null && root.right == null){
num++;
}
getLeafSize1(root.left);
getLeafSize1(root.right);
}
public int getLeafSize2(TreeNode root){
if(root == null){
return 0;
}
if(root.left==null && root.right == null){
return 1;
}
int left = getLeafSize1(root.left);
int right = getLeafSize2(root.right);
return left+right;
}
2.4.3 求第K层的节点数
解题思路:
当k=1,证明到达第K层,返回1.不需要再向下遍历。
实现代码:
public int getKLevelSize(TreeNode root,int k){
if(root == null){
return 0;
}
if(k==1){
return 1;
}
int left = getKLevelSize(root.left,k-1);
int right = getKLevelSize(root.right,k-1);
return left+right;
}
public int KLevelSize = 0;
public int getKLevelSize2(TreeNode root,int k){
if(root == null){
return 0;
}
if(k==1){
KLevelSize++;
}
getKLevelSize(root.left,k-1);
getKLevelSize(root.right,k-1);
}
三、二叉树基础练习题
3.1 相同的树
问题描述:
解题思路:
要点1:先判断结构。再判断值。使用前序遍历的方式。
要点2:如果两个根节点都为null,认为这两个节点相同,返回true
要点3:如果根节点有一个不为null,结果不相同,返回false
要点4:如果都不为null。先判断值是否相同,不相同就返回false。相同,则接着判断这两个根的左节点和右节点
实现代码:
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p == null && q == null){
return true;
}
if(p == null || q==null){
return false;
}
// 走到这,都不为null,判断值
if(p.val != q.val){
return false;
}else{
return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
}
}
3.2 另一棵树的子树
OJ链接:572. 另一棵树的子树 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:采用判断两个树是否相同的思路。
要点2:如果subRoot是个空,返回true。空树是任何树的子树
要点3:如果root是空,返回false。空树没有子树
要点4:先判断root这整棵树和subRoot是否相等、不相等,接着判断
root的左子树不为空的情况下,root的左子树这颗树和subRoot是否相等,不相等,接着判断
root的右子树不为空的情况下, root的右子树这颗树和subRoot是否相等
实现代码:
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(subRoot == null) return true;
if(root == null) return false;
if(isSameTree(root,subRoot)){
return true;
}
// 注意注意!!!判断的是:subRoot这棵树是否是root.left这棵树的子树
if(root.left != null && isSubtree(root.left,subRoot)){
return true;
}
if(root.right != null && isSubtree(root.right,subRoot)){
return true;
}
return false;
}
public boolean isSameTree(TreeNode root, TreeNode subRoot) {
if(root == null && subRoot==null) return true;
if(root == null || subRoot == null) return false;
if(root.val != subRoot.val) return false;
return isSameTree(root.left,subRoot.left)&&isSameTree(root.right,subRoot.right);
}
}
3.3 二叉树的最大深度(高度)
OJ链接:104. 二叉树的最大深度 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:root==null,返回0。
要点2:root不为空,返回1+max(左子树的高度,右子树的高度)
实现代码:
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
}
}
3.4 平衡二叉树
OJ链接:110. 平衡二叉树 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:求每个节点的左树的高度和右树的高度,相减判断是否满足平衡二叉树的要求,时间复杂度是O(n2)
实现代码:
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
if(Math.abs(left - right) > 1){
return false;
}
// root判断完后,需要接着判断左子树和右子树是否平衡
return isBalanced(root.left)&&isBalanced(root.right);
}
public int maxDepth(TreeNode root){
if(root == null){
return 0;
}
return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
}
}
解题思路:
要点1:在刚刚的解法中,时间复杂度是O(n2),现在要使时间复杂度使O(n).
可以在递归的过程中,计算出左右子树的高度,判断高度是否<=1,如果不满足,返回-1.则证明不平衡
实现代码:
class Solution {
public boolean isBalanced(TreeNode root) {
return maxDepth(root) >= 0;
}
public int maxDepth(TreeNode root){
if(root == null){
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
if(left >= 0 && right >=0 && Math.abs(left - right) <= 1){
return 1+Math.max(left,right);
}
return -1;
}
}
3.5 对称二叉树
OJ链接:101. 对称二叉树 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:如果root为空,则返回true。
要点2:写一个函数,输入是左树的根节点和右树的根节点
要点3:如果这连两个根节点不同时为null,返回false。同时为null,返回true。都不为null。接着判断两个根节点的值是否一样。一样接着判断左节点的左子树和右节点的右子树、左节点的右子树和右节点的左子树是否是对称的。
实现代码:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftNode,TreeNode rightNode){
if(leftNode == null && rightNode==null){
return true;
}
if(leftNode == null && rightNode != null || leftNode != null && rightNode == null ){
return false;
}
if(leftNode.val != rightNode.val){
return false;
}
return isSymmetricChild(leftNode.left,rightNode.right)&&isSymmetricChild(leftNode.right,rightNode.left);
}
}
四、二叉树进阶练习题
4.1 根据字符串构建二叉树
OJ链接:二叉树遍历_牛客题霸_牛客网 (nowcoder.com)
实现代码:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String str1 = scan.nextLine();
TreeNode root = creatTree(str1);
midorder(root);
}
}
public static void midorder(TreeNode root){
if(root == null){
return;
}
midorder(root.left);
System.out.print(root.val + " ");
midorder(root.right);
}
public static int i;
public static TreeNode creatTree(String str){
TreeNode root = null;
char ch1 = str.charAt(i);
if(ch1 != '#'){
root = new TreeNode(ch1);
i++;
root.left = creatTree(str);
root.right = creatTree(str);
}else{
i++;
}
return root;
}
}
class TreeNode{
public char val;
public TreeNode left;
public TreeNode right;
public TreeNode(char val){
this.val = val;
}
}
4.2 二叉树的最近公共祖先
OJ链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:如果p和q有一个等于root,就返回root。
要点2:判断p和q是否在root的左子树或者右子树中。
实现代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
// 判断p与q是否等于root,如果等于,则证明root就是最近的公共节点
if(p == root || q == root){
return root;
}
// 判断p、q是否在root的左树里面存在,如果p或q等于左树的某个节点,就会返回这个节点的值
TreeNode left = lowestCommonAncestor(root.left,p,q);
// 判断p、q是否在root的右树里面存在,如果p或q等于右树的某个节点,就会返回这个节点的值
TreeNode right = lowestCommonAncestor(root.right,p,q);
// 如果即在左树里面存在,又在右树里面存在,就证明一个是在root的左子树中。另一个是在root的右子树中。他们的最近公共祖先是root
if(left != null && right != null){
return root;
}
// right为null,证明p、q在右子树都不存在,所以都在左子树中。所以left是最近公共祖先
if(left != null && right == null){
return left;
}
if(left == null && right != null){
return right;
}
if(left == null && right == null){
return null;
}
return null;
}
}
4.3 二叉树与双向链表
OJ链接:二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
实现代码:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String str1 = scan.nextLine();
TreeNode root = creatTree(str1);
midorder(root);
}
}
public static void midorder(TreeNode root){
if(root == null){
return;
}
midorder(root.left);
System.out.print(root.val + " ");
midorder(root.right);
}
public static int i;
public static TreeNode creatTree(String str){
TreeNode root = null;
char ch1 = str.charAt(i);
if(ch1 != '#'){
root = new TreeNode(ch1);
i++;
root.left = creatTree(str);
root.right = creatTree(str);
}else{
i++;
}
return root;
}
}
class TreeNode{
public char val;
public TreeNode left;
public TreeNode right;
public TreeNode(char val){
this.val = val;
}
}
4.4 根据前序和中序遍历序列构建二叉树
OJ链接:
问题描述:
解题思路:
要点1:root==null,返回0。
要点2:root不为空,返回1+max(左子树的高度,右子树的高度)
实现代码:
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
}
}
4.5 根据前序和后序遍历序列构建二叉树
OJ链接:105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:root==null,返回0。
要点2:root不为空,返回1+max(左子树的高度,右子树的高度)
实现代码:
class Solution {
public int pos = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder == null || inorder == null){
return null;
}
return buildTree1(preorder,inorder,0,inorder.length-1);
}
public TreeNode buildTree1(int[] preorder,int[] inorder,int begin,int end){
if(begin > end){
return null;
}
// 拿到前序遍历结果的第一个值
int data = preorder[pos];
// 第一值就是root,构建一个节点
TreeNode root = new TreeNode(data);
int rootPos = findIndex(inorder,data,begin,end);
pos++;
root.left =buildTree1(preorder,inorder,begin,rootPos-1);
root.right = buildTree1(preorder,inorder,rootPos+1,end);
return root;
}
public int findIndex(int[] inorder,int data,int begin,int end){
// 查找data在中序遍历结果中的位置
for(int i= begin;i<=end;i++){
if(inorder[i] == data){
return i;
}
}
return -1;
}
}
4.6 根据二叉树创建字符串
OJ链接:606. 根据二叉树创建字符串 - 力扣(LeetCode)
问题描述:
解题思路:
要点1:root==null,返回0。
要点2:root不为空,返回1+max(左子树的高度,右子树的高度)
实现代码:
class Solution {
public StringBuffer sb = new StringBuffer();
public void tree2strChild(TreeNode root) {
if(root == null){
return;
}
sb.append(root.val);
if(root.left != null){
sb.append("(");
tree2strChild(root.left);
sb.append(")");
}else{
if(root.right == null){
return;
}else{
sb.append("()");
}
}
if(root.right == null){
return;
}else{
sb.append("(");
tree2strChild(root.right);
sb.append(")");
}
}
public String tree2str(TreeNode root) {
if(root == null){
return null;
}
tree2strChild(root);
return sb.toString();
}
}