目录
1、二叉树的最大深度
题目
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
思路
二叉树的遍历,深度优先搜索,前序遍历,先遍历根节点,再遍历左子树,再遍历右子树,函数中定义一个depth变量表示当前深度,如果当前节点不为空,更新当前最大深度后返回。
代码
java
class Solution {
int result=0;
public int maxDepth(TreeNode root) {
if(root==null) return 0;
dfs(root,0);
return result+1;
}
public void dfs(TreeNode root,int depth){
if(root==null) return;
if(result<depth) result=depth;
TreeNode left=root.left;
TreeNode right=root.right;
dfs(left,depth+1);
dfs(right,depth+1);
}
}
2、 叶子相似的树
题目
请考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个叶值序列 。如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是叶相似的。
如果给定的两个根结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。
思路
其实就是判断两个二叉树的叶子节点序列,深度优先搜索遍历,然后将两个二叉树的叶节点序列存储到list集合中,最后比较两个list集合,如果一模一样,就是叶相似的。
搜索函数中可以定义一个int值表示当前是哪一个二叉树。
代码
java
class Solution {
List<Integer> list1=new ArrayList<>();
List<Integer> list2=new ArrayList<>();
public boolean leafSimilar(TreeNode root1, TreeNode root2) {
boolean res=true;
dfs(root1,1);
dfs(root2,2);
int l1=list1.size(),l2=list2.size();
if(l1!=l2) return false;
for(int i=0;i<list1.size();i++){
if(list1.get(i)!=list2.get(i)) return false;
}
return res;
}
public void dfs(TreeNode root,int x){
if(root==null) return;
TreeNode left=root.left,right=root.right;
if(left==null&&right==null){
if(x==1) list1.add(root.val);
else list2.add(root.val);
return;
}
dfs(left,x);
dfs(right,x);
}
}
3、统计二叉树中好节点的数目
题目
给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。
「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
思路
在遍历函数中增加一个变量,表示当前路径下的最大值,如果当前值小于这个最大值,则不需要再往下遍历了。如果当前值大于这个值,为结果增加一个,当前节点就是好节点。
代码
java
class Solution {
int result=0;
public int goodNodes(TreeNode root) {
dfs(root,root.val);
return result;
}
public void dfs(TreeNode root,int maxCount){
if(root==null) return;
int x=root.val;
if(x>=maxCount){
result++;
maxCount=x;
}
dfs(root.left,maxCount);
dfs(root.right,maxCount);
}
}
4、 路径总和 III
题目
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
思路
这个很难,自己没做出来。
这是看的那个方法的思路,我觉得时间有点长,但是很好理解。
不一定以根节点开始,所以每个节点都可以作为根节点向下遍历,所以每一个节点都进行一次深度优先搜索,搜索时,先判断当前节点是否等于目标值,如果等于,加一,遍历左子树和右子树的值是否为sum-当前值(新的targetSum),如果当前节点就是目标值,左右子树判断目标值是否为零
注意最后一个通过用例很大,所以必须要定义类型为long,int过不去
代码
java
class Solution {
int res=0;
public int pathSum(TreeNode root, int targetSum) {
if(root!=null){
System.out.println(root.val);
long target=targetSum;
dfs(root,target);
pathSum(root.left,targetSum);
pathSum(root.right,targetSum);
}
return res;
}
private void dfs(TreeNode root,long sum){
if(root==null) return;
long x=root.val;
if(x==sum) res++;
//左子树和右子树的和是不是会为零
dfs(root.left,sum-x);
dfs(root.right,sum-x);
}
}
5、二叉树中的最长交错路径
题目
给你一棵以 root 为根的二叉树,二叉树中的交错路径定义如下:
选择二叉树中 任意 节点和一个方向(左或者右)。
如果前进方向为右,那么移动到当前节点的的右子节点,否则移动到它的左子节点。
改变前进方向:左变右或者右变左。
重复第二步和第三步,直到你在树中无法继续移动。
交错路径的长度定义为:访问过的节点数目 - 1(单个节点的路径长度为 0 )。
请你返回给定树中最长 交错路径 的长度。
思路
最初版思路:像上一个题一样,对于每一个结点都有可能开始,所以每一个结点都向左向右遍历,但是超时了。
后来版思路:在深度优先搜索函数里定义两个变量,分别表示它的方向和当前结点的数量。方向0表示向左,1表示向右,count是当前路径的节点数量。需要注意的是,如果从根节点向下一层遍历,被遍历的节点向下的方向不是向左就是向右,例如,根节点向右遍历,被遍历的节点下一个就是向左,但是这个节点也可能向右遍历有交错路径,所以每一个节点都要再向相反的方向以当前节点为起始节点重新遍历。
代码
最初版超时代码:
java
class Solution {
int res=0;
public int longestZigZag(TreeNode root) {
//与上一个相似,遍历每个结点,分别有左右两个方向
if(root!=null){
dfs(root,0,0);//向左,当前结点个数1个
dfs(root,1,0);
longestZigZag(root.left);
longestZigZag(root.right);
}
return res;
}
private void dfs(TreeNode root,int direction,int count){
if(root==null) {
if(res<count) res=count;
return;
}
if(direction==0&&root.left!=null){
dfs(root.left,1-direction,count+1);
}
else if(direction==1&&root.right!=null){
dfs(root.right,1-direction,count+1);
}
else{
if(res<count) res=count;
return;
}
}
}
后来版代码:
java
class Solution{
int res=0;
public int longestZigZag(TreeNode root){
dfs(root,0,0);//向左遍历
dfs(root,1,0);//向右遍历
return res;
}
private void dfs(TreeNode root,int dir,int count){
if(root==null) return;
if(count>res) res=count; //记录当前路径的数量
if(dir==0){//dir是零,说明是向左遍历来的,下一次向右遍历
dfs(root.right,1,count+1);
dfs(root.left,0,1);//从当前节点向左遍历
}
else{
dfs(root.left,0,count+1);
dfs(root.right,1,1);
}
}
}
6、二叉树的最近公共祖先
题目
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:"对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。"
思路
没做出来,看的代码随想录卡尔的解析视频,思路明确。
使用后续遍历,先遍历左子树,后遍历右子树,最后遍历根节点
涉及到回溯,简单地讲,对于每一个节点,如果当前节点为p或q,返回当前节点,如果不是,
遍历它的左右子树,有没有p或q,有以下几种情况:
左右子树为空,返回空值
左子树有,右子树没有,返回右子树
左子树没有,右子树有,返回左子树
左右子树都有,返回当前节点
乍一看文字可能理解不了,推荐大家去b站看讲解视频,更清楚
代码
java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
TreeNode x= func(root,p,q);
return x;
}
private TreeNode func(TreeNode root,TreeNode p,TreeNode q){
if(root==null) return null;
//终止情况,遇见了p/q,向上返回
//如果一个结点左右值都不为空就是目标结果
if(root==p||root==q) return root;
TreeNode left = func(root.left,p,q);//左子树有没有p/q
TreeNode right = func(root.right,p,q);//右子树有没有p/q
if(left!=null&&right!=null){
//root就是最近公共祖先
return root;
}
else if(left==null&&right!=null){
//左子树没有目标结点,右子树有目标节点
return right;
}
else if(right==null&&left!=null){
return left;
}
else{
return null;
}
}
}
7、二叉树的右视图
题目
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
思路
一开始想的是一直从根节点向右遍历,错的,因为它要的是每一层的最右边的节点值,层序遍历和前序遍历类似,但是前序遍历是根节点,左子树和右子树。层序遍历需要将左右结点存好,需要依次遍历这一层的左节点和右节点。这个题比层序遍历简单,区别就是将这一行的最右一个保存,而不是保存每一个。
代码
java
class Solution {
List<Integer> list=new ArrayList<>();//保存最终结果
Queue<TreeNode> queue=new LinkedList<>();
Queue<Integer> queueInteger=new LinkedList<>();
//保存中间结点,栈/队列
public List<Integer> rightSideView(TreeNode root) {
if(root==null) return list;
queue.offer(root);
queueInteger.offer(0);
while(!queue.isEmpty()){
//将queue中的结点从头弹出,在尾加入左右结点
TreeNode node=queue.poll();
if(node==null) break;
int x=queueInteger.poll();
//如果queueInteger里,x层的元素没有了
//说明这个node.val就是x层最后一个
if(queueInteger.peek()==null||queueInteger.peek()>x) list.add(node.val);
if(node.left!=null){
queue.offer(node.left);
queueInteger.offer(x+1);
}
if(node.right!=null){
queue.offer(node.right);
queueInteger.offer(x+1);
}
}
return list;
}
}
8、 最大层内元素和
题目
给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。
请返回层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。
思路
跟上一个一样,层序遍历,只不过要求和
代码
java
class Solution {
public int maxLevelSum(TreeNode root) {
int res=0; //层数记录
int count=Integer.MIN_VALUE; //最大值记录
Queue<TreeNode> queueNode=new LinkedList<>();
Queue<Integer> queueInteger=new LinkedList<>();
queueNode.offer(root);
queueInteger.offer(1);
TreeNode node;
int x;
int nowCount=0;
while(!queueNode.isEmpty()){
node=queueNode.poll();
if(node==null) break;
x=queueInteger.poll();
nowCount+=node.val;
if(queueInteger.peek()==null||queueInteger.peek()>x){
//保存x
if(nowCount>count){
count=nowCount;
res=x;
}
nowCount=0;
}
if(node.left!=null){
queueNode.offer(node.left);
queueInteger.offer(x+1);
}
if(node.right!=null){
queueNode.offer(node.right);
queueInteger.offer(x+1);
}
}
return res;
}
}
9、二叉搜索树中的搜索
题目
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
思路
需要直到二叉搜索树是什么,就很简单了。
二叉搜索树,左子树所有节点均小于根节点,右子树所有结点均大于根节点,左右子树都是二叉搜索树。
所以,根据当前节点值,只要往一个方向遍历就可以了。
代码
java
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
//如果小于当前,遍历左子树,如果大于当前,遍历右子树
TreeNode res=dfs(root,val);
return res;
}
private TreeNode dfs(TreeNode root,int val){
if(root==null) return null;
TreeNode x;
if(val<root.val){
x=dfs(root.left,val);
}
else if(val>root.val) {
x=dfs(root.right,val);
}
else return root;
return x;
}
}
10、删除二叉搜索树中的节点
题目
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
思路
难点在于如何删除一个节点之后,调整二叉搜索树,使得剩余的树是一个二叉搜索树。
先遍历到key值
1、是根节点,无所谓
2、有一个子树,下一个直接代替当前值
3、左右子树均不为空
(1)要删除结点的左子树都放在右子树最小节点的左子树上
(2)要删除结点的右子树都 放在左子树最大结点的右子树上
使用3.1方法
单独处理的如果根节点是删除节点的情况
代码
java
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null) return null;
if(root.val==key){
if(root.left==null&&root.right==null)
return null;
if(root.left!=null&&root.right==null)
return root.left;
if(root.left==null&&root.right!=null)
return root.right;
else{
TreeNode m=root.left;
while(m.right!=null){
m=m.right;
}
m.right=root.right;
return root.left;
}
}
func(root,key,null,0);
return root;
}
private void func(TreeNode root,int val,TreeNode parent,int direction){
if(root==null) return;
if(val>root.val) func(root.right,val,root,1);
else if(val<root.val) func(root.left,val,root,0);
else{
//获取右子树的最小结点,对于右子树一直向左遍历
//如果没有左子树了,就是最小的
if(root.left==null&&root.right==null){
if(direction==0) parent.left=null;
else if(direction==1) parent.right=null;
}
else if(root.left!=null&&root.right==null){
if(direction==0) parent.left=root.left;
else parent.right=root.left;
}
else if(root.right!=null&&root.left==null){
if(direction==0) parent.left=root.right;
else parent.right=root.right;
}
else if(root.left!=null&&root.right!=null){
//父节点的下一个是删除节点的左节点
//遍历到左节点的右子树
//直到右子树为空,将删除节点的右子树放在它后面'
TreeNode m=root.left;
while(m.right!=null){
m=m.right;
}
m.right=root.right;
if(direction==0) parent.left=root.left;
else parent.right=root.left;
}
return;
}
}
}