相关概念
树是数据结构中比较重要的一个点,而二叉树是树学习中的难点,且有各种二叉树的延伸。
在定义中,二叉树是每个结点最多只有两个子树的结构(左子树和右子树),在这里给出二叉树的结构体。
//Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
一般在遇到二叉树相关类型题目时,使用递归思路是解题的关键(我觉得碰到树的题用递归就完事了。。。)当然使用其他方法也是可行的。
面试题55-Ⅰ 二叉树的深度
问题描述:输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
思路:
这种就是练手题了,递归直接调用简单,非递归的话可以考虑使用队列层次遍历,直接上代码。
递归
public int maxDepth(TreeNode root) {
if(root==null) return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
层次遍历
public int maxDepth(TreeNode root) {
int depth = 0;
if (root == null)
return depth;
Queue<TreeNode> queue = new LinkedList<>();
// 根结点入队
queue.add(root);
while (!queue.isEmpty()) {
int n = queue.size();
for (int i = 0; i < n; i++) {
TreeNode curNode = queue.poll();
if (curNode.left != null)
queue.add(curNode.left);
if (curNode.right != null)
queue.add(curNode.right);
}
depth++;
}
return depth;
}
面试题55-Ⅱ 平衡二叉树
问题描述:输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:这里需要递归判断其左右子树是否是平衡二叉树,依据平衡二叉树的定义,其就是在Ⅰ中求深度之后再加一个深度差的计算。
public boolean isBalanced(TreeNode root) {
if(root==null) return true;
if(Math.abs(maxDepth(root.left)-maxDepth(root.right))<=1){
return isBalanced(root.left)&&isBalanced(root.right);
}
return false;
}
public int maxDepth(TreeNode root){
if(root==null) return 0;
return Math.max(maxDepth(root.right),maxDepth(root.left))+1;
}
94.二叉树的中序遍历
问题描述:给定一个二叉树,返回它的中序 遍历。
思路:二叉树的遍历尤其是中序遍历应该是比较重要的知识点,其遍历顺序可以是左子树→根节点→右子树,递归实现比较简单,非递归可以考虑使用栈。
递归实现
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
helper(root,list);
return list;
}
public void helper(TreeNode root,List <Integer> list){
if(root!=null){
if(root.left!=null){
helper(root.left,list);
//list.add(root.val);
}
list.add(root.val);//只要遍历左节点为空则将当前值加入list;
if(root.right!=null)
helper(root.right,list);
}
}
非递归实现(栈)
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
while(root!=null||!stack.isEmpty()){
while(root!=null){
stack.push(root);//根结点先进后出
root = root.left;//遍历左结点
}
root = stack.pop();//遍历到最左结点先出栈
list.add(root.val);//出栈入表
root = root.right;
}
return list;
}
98.验证二叉搜索树
**问题描述:**给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
1、节点的左子树只包含小于当前节点的数。
2、节点的右子树只包含大于当前节点的数。
3、所有左子树和右子树自身必须也是二叉搜索树。
思路:二叉树的中序遍历可以返回一个递增数组,只需要比较当前root值与上一个存储值进行比较即可。
代码实现
double last=-Double.MAX_VALUE;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
if(isValidBST(root.left)){
if(root.val>last){
last=root.val;
return isValidBST(root.right);
}
}
return false;
//return helper(root);
}
面试题54.二叉搜索树的第k大结点
**问题描述:**给定一棵二叉搜索树,请找出其中第k大的节点。
**思路:**因为中序遍历返回的是递增序列,即第k大则为倒数第k个数。
代码实现
public int kthLargest(TreeNode root,int k){
List<Integer> list=new ArrayList<>();
helper(root,list);
return list.get(list.size()-k);
}
public void helper(TreeNode root,List<Integer> list){
if(root==null) return;
if(root.left!=null) helper(root.left,list);
list.add(root.val);
if(root.right!=null) helper(root.right,list);
}
另一种想法是我直接先遍历右子树,并用一个计数标志,当计数到第k个结点时就是我要输出的数,这样我直接可以在遍历过程中找到目标值。
private int count=0,ans=0;
public int kthLargest(TreeNode root, int k) {
//count=k;
helper(root,k);
return ans;
}
public void helper(TreeNode root,int k){
if(root==null) return;
helper(root.right,k);
//count--;
if(++count==k){
ans = root.val;
}
if(root.left!=null) helper(root.left,k);
}
二叉搜索树中的众数
**问题描述:**给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
假定 BST 有如下定义:
结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
**思路:**既然是二叉搜索树,那么只需要递归比较当前结点与前驱结点是否相同,因为众数可能不唯一,所以还需要设置变量用来存储众数出现最多的次数以及当前数出现的次数。
代码实现
TreeNode preNode=null;//设置前驱结点
int max=0;//统计已知众数出现最多的次数
int count=0;//统计当前数出现的次数
//int size;//统计有多少种众数
List<Integer> list =new ArrayList<>();
public int[] findMode(TreeNode root) {
helper(root);
int []arr=new int[list.size()];
// int i=0;
// for(int num:list){
// arr[i++]=num;
// }
for(int i=0;i<list.size();i++){
arr[i]=list.get(i);
}
return arr;
}
public void helper(TreeNode root){
if(root!=null){
helper(root.left);
if(preNode==null||preNode.val!=root.val)
count=1;
else if(preNode.val==root.val)
count++;
if(max<count){
//size=1;
max=count;
list.clear();//更新众数
list.add(root.val);
}
else if(max==count){
//size++;
list.add(root.val);
}
preNode=root;
helper(root.right);
}
}
236.二叉树的最近公共祖先
问题描述:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
思路:
注意p,q必然存在树内, 且所有节点的值唯一!!!
递归思想, 对以root为根的(子)树进行查找p和q, 如果root == null || p || q 直接返回root
表示对于当前树的查找已经完毕, 否则对左右子树进行查找, 根据左右子树的返回值判断:
1. 左右子树的返回值都不为null, 那么说明两个结点分别在两个子树之中,那么直接返回root;
2. 如果左右子树返回值只有一个不为null, 说明只有p和q存在与左或右子树中, 直接返回最先找到的那个结点;
3. 左右子树返回值均为null, p和q均不在树中, 返回null(只是将这种情况列出去,按照题目的意思应该不存在这种情况)
直接上递归代码:
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 null;//这个情况表示p、q不在树中
else if(left!=null&&right!=null) return root;
else return left==null?right:left;//左子树空则在右
}
总结
二叉树的类型有很多,在这里主要写的都是使用中序遍历的一个情况,当然了只要弄懂了二叉树的原理,合理使用递归或迭代也可以实现其它类型的二叉树。