文章:代码随想录
状态:其实就是要熟悉各种遍历方式,前中后递归遍历,非递归和层序遍历。
下面就会记录每一道题的不同遍历方法,逻辑就和遍历相同,注释写明了思考过程:
最大深度:
//层序遍历方法,每遍历一层count++,最后遍历了多少层最大深度就是多少.
public int maxDepthLevel(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int ans = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i=0;i<size;i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
ans++;
}
return ans;
}
//高度是指当前节点到叶子节点的节点树,也就是从下往上计数.用后续。
//深度是指当前节点到根节点的节点数,也就是从上往下计数,用前序。计数都是从1开始。
//其实根节点的高度就是这颗树的最大深度
//后序遍历,递归法,从下往上计数,求根节点高度
public int maxDepthPost(TreeNode root) {
return getDepthPost(root);
}
private int getDepthPost(TreeNode node) {
if(node==null){return 0;}
//左右
int left=getDepthPost(node.left);
int right=getDepthPost(node.right);
//中
return Math.max(left,right)+1;
}
//后续遍历,递归法,从上往下计数,求深度,最后返回给根节点。
public int maxDepthPost2(TreeNode root) {
return getDepthPost(root,0);
}
private int getDepthPost(TreeNode node, int depth) {
if(node ==null){return depth-1;}
//左右
int left=getDepthPost(node.left,depth+1);
int right=getDepthPost(node.right,++depth);
//中
return Math.max(left,right);
}
//前序递归遍历,因为是前序左右子树的最大深度最后不会返回给中,无法对比.所以需要自定义一个变量来记录,直接遍历到底部不断更新这个变量就可以.
int maxDepth=0;
public int maxDepthPre(TreeNode root) {
getDepthPre(root,1);
return maxDepth;
}
private void getDepthPre(TreeNode node, int depth) {
if(node==null){return;}
//中(数据处理)
maxDepth=maxDepth>depth?maxDepth:depth;
depth++;
//左右
getDepthPre(node.left,depth);
getDepthPre(node.right,depth);
}
//和前序同理,中序也是一样的逻辑.因为变量是全局的,只需要将二叉树全部遍历一次就能得到最大深度.
public int maxDepthIn(TreeNode root) {
//这里这里传入0,因为数据处理位置不同,depth在递归里面是先++的,第一层的初始值进去也是1.
getDepthIn(root,0);
return maxDepth;
}
private void getDepthIn(TreeNode node, int depth) {
if(node==null){return;}
depth++;
//左右
getDepthPre(node.left,depth);
//中(数据处理)
maxDepth=maxDepth>depth?maxDepth:depth;
getDepthPre(node.right,depth);
}
最小深度:
//层序遍历
public int minDepthLevel(TreeNode root) {
if (root == null) {
return 0;
}
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
int ans = 1;
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
TreeNode node = queue.poll();
if(node.left==null && node.right==null){return ans;}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
size--;
}
ans++;
}
return ans;
}
//后序遍历,两种方式:逻辑相同
public int minDepthPost1(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = minDepthPost1(root.left);
int rightDepth = minDepthPost1(root.right);
//如果左子树为空,那么就不考虑左子树的情况,只返回右子树的最小值
if (root.left == null) {
return rightDepth + 1;
}
//如果右子树为空,那么就不考虑右子树的情况,只返回左子树的最小值
if (root.right == null) {
return leftDepth + 1;
}
// 左右结点都不为null,那就取左右子树的最小值返回。
return Math.min(leftDepth, rightDepth) + 1;
}
public int minDepthPost2(TreeNode root) {
if (root == null) {
return 0;
}
if(root.left==null && root.right==null){return 1;}
int minDepth = Integer.MAX_VALUE;
//如果左子树不为空,那么计算左子树不为空的最小深度,更新minDepth值
if (root.left != null) {
minDepth=Math.min(minDepthPost2(root.left),minDepth);
}
//如果左子树不为空,那么计算左子树不为空的最小深度,更新minDepth值
if (root.right != null) {
minDepth=Math.min(minDepthPost2(root.right),minDepth);
}
//为什么这里用!=null而不是上面的==null呢?
//因为结尾是直接返回minDepth+1的,没有对左右子树都不为空的情况做判断,假设都左右子树都不为空,如果是==null来判断,
//那最后返回的就是Integer.MAX_VALUE+1的值了,所以判断条件不同.
//如果想写==null来判断就要把,递归调用的left,right写在if外面,最后再像第一种方法一样最后用Math.min再来判断下left和right的最小值.
//但是这种写法不能应对左右子树都为空的情况,这也是为什么这种写法要加上左右都为空的情况返回1.
return minDepth + 1;
}
完全二叉树:
这里可以利用完全二叉树的特性
//后序遍历
public int countNodes(TreeNode root) {
if(root==null){return 0;}
int left=countNodes(root.left);
int right=countNodes(root.right);
//+1是指加上自己本身,中节点.
return left+right+1;
}
//前中序的逻辑
//或者新定义一个方法传入count做++也是一样的。
int count=0;
public int countNodesPre(TreeNode root) {
if(root==null){return 0;}
count++;
countNodes(root.left);
countNodes(root.right);
return count;
}
//也可以用层序遍历,逻辑都是一样的.
//但是这道题强调的是我们要利用完全二叉树的特性
//完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
//对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
//对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树(叶子节点),然后依然可以按照情况1来计算。
//那么对于每一个节点,我们可以判断它的左右子树是否是满二叉树,如果是则直接通过公式2^depth-1计算个数返回。如果不是则持续向下递归
//那怎么判断节点的左右子树是不是满二叉树呢?可以通过一直向左走和一直向右走看这个节点到两边叶子节点的深度是否一样,如果一样,则这个节点下的左右子树都是满二叉树
//然后就可以通过当前节点的深度直接计算个数返回给上一层.
public int countNodesFullChildren(TreeNode root) {
if(root==null){return 0;}
int leftDepth = 0,rightDepth=0;
//定义左右指针来遍历当前节点的左右子树的深度,判断是不是满二叉树
TreeNode leftChild = root,rightChild=root;
while (leftChild.left!=null){
leftChild=leftChild.left;
leftDepth++;
}
while (rightChild.right!=null){
rightChild=rightChild.right;
rightDepth++;
}
//如果左右都是满二叉子树则直接按照公式计算
//2左移几位就是2乘以2的几次方(2^depth)-1.例如左移0位,就相当于2*2^0=2;3左移2位,就相当于3*2^2=12;
if(leftDepth==rightDepth){return (2<<leftDepth)-1;}
//如果当前节点左右子树不是满二叉树,则需要继续往下遍历去寻找。
//那如果一直找到叶子节点都没找到,实际上也就是普通的后序遍历.但是是完全二叉树不存在左右子树都一直找不到的情况,这也就是我们说的利用完全二叉树的特性。
int leftNumber=countNodesFullChildren(root.left);
int rightNumber=countNodesFullChildren(root.right);
return leftNumber+rightNumber+1;
}
//层序遍历
public int countNodesLevel(TreeNode root) {
if(root==null){return 0;}
Queue<TreeNode> levelRecord=new LinkedList<>();
levelRecord.offer(root);
int count=0;
while(!levelRecord.isEmpty()){
int size=levelRecord.size();
count+=size;
for(int i=0;i<size;i++){
TreeNode top=levelRecord.poll();
if(top.left!=null){levelRecord.offer(top.left);}
if(top.right!=null){levelRecord.offer(top.right);}
}
}
return count;
}