257、二叉树的所有路径
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
class Solution { //递归+回溯
public List<String> binaryTreePaths(TreeNode root) {
List<String> result= new ArrayList<>(); //存放结果
List<Integer> path =new ArrayList<>(); //存放每一条路径
if(root==null) return result;
trans(root,result,path);
return result;
}
private void trans(TreeNode root,List<String> result,List<Integer>path)
{
path.add(root.val);//根
if(root.left==null && root.right==null) //当前结点是叶结点,说明有一条完整路径
{
StringBuilder sb =new StringBuilder();
for(int i=0;i<path.size()-1;i++)
{
sb.append(path.get(i));
sb.append("->");
}
sb.append(path.get(path.size()-1));
result.add(sb.toString());
}
if(root.left!=null) //左
{
trans(root.left,result,path); //递归
path.remove(path.size()-1); //回溯
}
if(root.right!=null) //右
{
trans(root.right,result,path);
path.remove(path.size()-1);
}
}
}
迭代法:用一个栈记录
// 解法二
class Solution {
/**
* 迭代法:本质上就是二叉树的先序遍历
*/
public List<String> binaryTreePaths(TreeNode root) {
List<String> result = new ArrayList<>();
if (root == null)
return result;
Stack<Object> stack = new Stack<>();
// 节点和路径同时入栈
stack.push(root);
stack.push(root.val + ""); //存的是字符串
while (!stack.isEmpty()) {
// 节点和路径同时出栈
String path = (String) stack.pop();
TreeNode node = (TreeNode) stack.pop();
// 若找到叶子节点
if (node.left == null && node.right == null) {
result.add(path);
}
//右子节点不为空
if (node.right != null) {
stack.push(node.right);
stack.push(path + "->" + node.right.val);
}
//左子节点不为空
if (node.left != null) {
stack.push(node.left);
stack.push(path + "->" + node.left.val);
}
}
return result;
}
}
#124、二叉树中的最大路径和
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
class Solution {
int maxSum=Integer.MIN_VAlUE;
//计算每个结点的贡献值,然后路径等于该节点的值与该节点的左右子节点的最大贡献值
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
//后序遍历求每个结点的最大贡献值,在后续遍历的基础上更新答案
public int maxGain(TreeNode node)
{
if(node==null)
return 0;
//递归计算左右结点的最大贡献值
int leftGain=maxGain(node.left);
// 只有在最大贡献值大于 0 时,才会选取对应子节点
if(leftGain<0)
leftGain=0;
int rightGain=maxGain(node.right);
if(rightGain<0)
rightGain=0;
//和最大直径一样,相当于是对每个结点遍历,求以每个结点为根的最大路径和
int nodeGain=node.val+leftGain+rightGain;
maxSum=Math.max(nodeGain,maxSum);
return node.val+Math.max(leftGain,rightGain); //每次递归返回的是本结点的值+左右结点的一个最大值作为贡献值
}
}
404、左叶子之和
给定二叉树的根节点 root ,返回所有左叶子之和。
示例 1:
输入: root = [3,9,20,null,null,15,7]
输出: 24
就是遍历+通过父节点判断本节点的属性
平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。
递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和
递归遍历
class Solution { //递归遍历,左右中
public int sumOfLeftLeaves(TreeNode root) {
if(root==null) return 0; //要有返回条件
int leftValue = sumOfLeftLeaves(root.left); //左
int rightValue =sumOfLeftLeaves(root.right); //右
int midValue=0;
if(root.left!=null &&root.left.left==null &&root.left.right==null)
midValue+=root.left.val; //中
return midValue+leftValue+rightValue;
}
}
迭代法,层序遍历:
class Solution { //使用层序遍历
public int sumOfLeftLeaves(TreeNode root) {
Queue<TreeNode> queue =new LinkedList<>();
queue.add(root);
int sum=0;
while(!queue.isEmpty())
{
int len=queue.size();
while(len>0)
{
TreeNode node =queue.poll();
if(node.left!=null)
{
if(node.left.left==null&&node.left.right==null)
sum+=node.left.val;
queue.add(node.left);
}
if(node.right!=null)
{
queue.add(node.right);
}
len--;
}
}
return sum;
}
}
迭代法
class Solution { //迭代法:使用栈,相当于前序遍历
public int sumOfLeftLeaves(TreeNode root) {
if(root==null) return 0;
Stack<TreeNode> stack =new Stack<>();
stack.push(root);
int sum=0;
while(!stack.isEmpty())
{
TreeNode node =stack.pop();
if(node.left!=null &&node.left.left==null &&node.left.right==null)
{
sum+=node.left.val;
}
if(node.right!=null) stack.push(node.right);
//先右结点入栈,在左结点入栈
if(node.left!=null) stack.push(node.left);
}
return sum;
}
}
513、找树左下角的值
给定一个二叉树的 根节点root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
使用层序遍历,先统计高度,在找到最后一层的第一个结点
class Solution { //层序遍历
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue =new LinkedList<>();
queue.add(root);
int hight=0;
while(!queue.isEmpty()) //先统计高度
{
hight++;
int len=queue.size();
while(len>0)
{
TreeNode node =queue.poll();
if(node.left!=null)
queue.add(node.left);
if(node.right!=null)
queue.add(node.right);
len--;
}
}
int n=0;
queue.add(root);
while(!queue.isEmpty()) //找最底层结点
{
n++;
int len=queue.size();
int sum=len;
while(len>0)
{
TreeNode node =queue.poll();
if(n==hight && len==sum) //最底层的第一个结点
return node.val;
if(node.left!=null)
queue.add(node.left);
if(node.right!=null)
queue.add(node.right);
len--;
}
}
return 0;
}
}
112、路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
特别是有些时候 递归函数的返回值是bool类型,一些同学会疑惑为啥要加这个,其实就是为了找到一条边立刻返回。
其实还有一种就是后序遍历需要根据左右递归的返回值推出中间节点的状态,这种需要有返回值,例如222.完全二叉树 (opens new window),110.平衡二叉树 (opens new window),这几道我们之前也讲过。
递归法:前序遍历
class Solution {
//递归法,用targetSum 减去结点的值,看能否为0
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null)
return false //出口
targetSum-=root.val; //中
if(root.left==null &&root.right==null) //到达了叶子结点
{
if(targetSum==0) return true; //并且为0
}
if(root.left!=null) //递归左结点
{
boolean left=hasPathSum(root.left,targetSum);
if(left)
return true;
}
if(root.right!=null) //递归右结点
{
boolean right=hasPathSum(root.right,targetSum);
if(right)
return true;
}
return false;
}
}
class Solution {
//迭代法,使用两个栈,分别存储结点和路径和
//本质上是前序遍历的迭代法
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
Stack<TreeNode> stack1 =new Stack<>();
Stack<Integer> stack2=new Stack<>();
stack1.push(root);
stack2.push(root.val); //存储每个结点的路径和
while(!stack1.isEmpty() && !stack2.isEmpty())
{
TreeNode node =stack1.pop();
int sum=stack2.pop();
if(node.left==null&&node.right==null) //遇到了根结点
{
if(sum==targetSum)
return true;
}
if(node.right!=null)
{
stack1.push(node.right);
stack2.push(sum+node.right.val);
}
if(node.left!=null)
{
stack1.push(node.left);
stack2.push(sum+node.left.val);
}
}
return false;
}
}
#113、路径总和二
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
递归法前序遍历
class solution {
public List<List<Integer>> pathsum(TreeNode root, int targetsum) {
List<List<Integer>> res = new ArrayList<>();
if (root == null)
return res; // 非空判断
List<Integer> path = new LinkedList<>();
preorderdfs(root, targetsum, res, path);
return res;
}
public void preorderdfs(TreeNode root, int targetsum, List<List<Integer>> res, List<Integer> path) {
if(root==null)
return;
path.add(root.val);
//遇到了叶子节点
if(root.left == null && root.right == null) {
//找到了和为 targetsum 的路径
if (targetsum - root.val == 0) {
res.add(new ArrayList<>(path));
//result.add(path);
//注意!!!这里放入result的是path地址,改变path后,result的结果也会改
}
}
if (root.left != null) {
preorderdfs(root.left, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preorderdfs(root.right, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}
#437、路径总和三
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
示例 1:
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
示例 2:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:3
class Solution {
//方法一:深度优先搜索,对每个结点进行搜索
public int pathSum(TreeNode root, int targetSum) {
if(root==null)
return 0;
long target=(long) targetSum;
int ret=rootSum(root,target); //中
ret+=pathSum(root.left,(int)target); //左
ret+=pathSum(root.right,(int)target); //右
return ret;
}
//统计从每个结点出发有多少符合的路径
public int rootSum(TreeNode root,long targetSum)
{
int ret=0;
if(root==null)
return 0;
if(root.val==targetSum)
ret++;
ret+=rootSum(root.left,targetSum-root.val);
ret+=rootSum(root.right,targetSum-root.val);
return ret;
}
}
106、从中序与后序遍历序列构造二叉树
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例 1:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
构造二叉树有三个注意的点:
- 分割时候,坚持区间不变量原则,左闭右开,或者左闭又闭。
- 分割的时候,注意后序 或者 前序已经有一个节点作为中间节点了,不能继续使用了。
- 如何使用切割后的后序数组来切合中序数组?利用中序数组大小一定是和后序数组的大小相同这一特点来进行切割。
构造树的时候使用的是前序遍历,中左右的遍历。
用两个指针来分割区间,返回值是一个结点来进行构造,注意边界!
class Solution {
Map<Integer,Integer> map=new HashMap<>(); //用来快速根据值查找中序下标
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i=0;i<inorder.length;i++)
map.put(inorder[i],i);
return findNode(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
}
//参数的范围都是左闭右闭原则
private TreeNode findNode(int[] inorder,int inBegin,int inEnd,int []postorder,int postBegin,int postEnd)
{
if(inBegin>inEnd || postBegin>postEnd) return null;
int rootIndex =map.get(postorder[postEnd]);
TreeNode root =new TreeNode(postorder[postEnd]); //构造结点
int len=rootIndex-inBegin; //中序左子树的个数
root.left=findNode(inorder,inBegin,rootIndex-1,postorder,postBegin,postBegin+len-1);
root.right=findNode(inorder,rootIndex+1,inEnd,postorder,postBegin+len,postEnd-1); //最后一个元素已经成为根结点
return root;
}
}
#105、从前序与中序遍历序列构造二叉树
class Solution {
Map<Integer,Integer> map=new HashMap<>(); //用来快速根据值查找中序下标
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++)
map.put(inorder[i],i);
return findNode(inorder,0,inorder.length-1,preorder,0,preorder.length-1);
}
//参数的范围都是左闭右闭原则
private TreeNode findNode(int[] inorder,int inBegin,int inEnd,int []preorder,int preBegin,int preEnd)
{
if(inBegin>inEnd || preBegin>preEnd) return null;
int rootIndex =map.get(preorder[preBegin]); //查找下标
TreeNode root =new TreeNode(preorder[preBegin]); //构造结点
int len=rootIndex-inBegin; //中序左子树的个数
root.left=findNode(inorder,inBegin,rootIndex-1,preorder,preBegin+1,preBegin+len);
root.right=findNode(inorder,rootIndex+1,inEnd,preorder,preBegin+len+1,preEnd);
return root;
}
}
654、最大二叉树
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
- 创建一个根节点,其值为 nums 中的最大值。
- 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums__ 构建的最大二叉树** 。
示例 1:
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
class Solution {
//使用两个指针,记录,直接利用原数组
//左闭右闭
private TreeNode findNode(int[] nums,int left,int right)
{
if(left>right) return null;
int rootIndex=left;
//注意是在子数组中寻找
for(int i=left;i<=right;i++) //寻找最大值,即分界
{
if(nums[rootIndex]<nums[i])
rootIndex=i;
}
TreeNode root =new TreeNode(nums[rootIndex]); //建立结点
root.left=findNode(nums,left,rootIndex-1);
root.right=findNode(nums,rootIndex+1,right);
return root;
}
public TreeNode constructMaximumBinaryTree(int[] nums) {
return findNode(nums,0,nums.length-1);
}
}
构造二叉树,一般都需要返回值,这个返回值就是树的结点。
617、合并二叉树
你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
示例 1:
输入:root1 = [1,3,2,5], root2 = [2,1,3,null,4,null,7]
输出:[3,4,5,5,4,null,7]
递归法:前中后序都是可以的
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1==null) return root2;
if(root2==null) return root1;
root1.val+=root2.val;
root1.left=mergeTrees(root1.left,root2.left);
root1.right=mergeTrees(root1.right,root2.right);
return root1;
}
}
迭代法:使用一个队列
class Solution { //使用队列迭代,队列中存储的是公共的部分,注意不是层序遍历
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1==null &&root2==null) return null;
if(root1!=null &&root2==null) return root1;
if(root1==null &&root2!=null) return root2;
Queue<TreeNode> queue = new LinkedList();
queue.add(root1);
queue.add(root2);
while(!queue.isEmpty())
{
TreeNode node1 =queue.poll(); //相应的两个结点都有的话,入队并出队
TreeNode node2 =queue.poll();
node1.val+=node2.val;
if(node1.left!=null && node2.left!=null) //都有左节点
{
queue.add(node1.left);
queue.add(node2.left);
}
if(node1.right!=null && node2.right!=null) //都有右节点
{
queue.add(node1.right);
queue.add(node2.right);
}
if(node1.left==null && node2.left!=null)
//root1的左节点为空,直接赋值
{
node1.left=node2.left;
}
if(node1.right==null && node2.right!=null)
//root1的右节点为空,直接赋值
{
node1.right=node2.right;
}
//其余情况的root1不需要改动
}
return root1;
}
}
700、二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
示例 1:
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
二叉搜索树,利用二叉搜索树有序性的特点
二叉搜索树,一定要利用有序性的特点。
class Solution { //递归法搜索
public TreeNode searchBST(TreeNode root, int val) {
if(root==null) return null; //递归出口
if(root.val==val) return root;
else if(root.val>val) return searchBST(root.left,val);
else if(root.val<val) return searchBST(root.right,val);
return null; //找不到就返回null
}
}
class Solution {
//迭代,利用二叉搜索树特点,优化,可以不需要栈
//即在二叉搜索树上进行查找
public TreeNode searchBST(TreeNode root, int val) {
while (root != null) //根不为null的时候一直循环
{
if (val < root.val) root = root.left;
else if (val > root.val) root = root.right;
else return root;
}
return null;
}
}
#98、验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
用范围来判断:
class Solution {
//递归法:注意二叉搜索树 不只是左右结点的关系,而是左右子树的关系
//是左子树的所有结点都小于根,右子树的所有结点都大于根
//所以用区间判断,都是开区间
//由于测试用例中存在最小的int,而二叉搜索树不能含有相同的元素,所以用long的最小值
public boolean isValidBST(TreeNode root) {
return isBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
//对于整个树,节点的值肯定在最大值和最小值之间
//然后不断调整左右范围
}
private boolean isBST(TreeNode root,long lower,long upper)
{
if(root==null) return true;
if(root.val<=lower || root.val>=upper)
return false;
return isBST(root.left,lower,root.val) &&isBST(root.right,root.val,upper);
}
}
在中序遍历的基础上判断是否有序
二叉树经常使用到pre和cur双指针
class Solution {
//递归法,中序遍历
//递归实现中序遍历,使用一个pre来记录前一个结点,用来判断有序
TreeNode pre=null;
public boolean isValidBST(TreeNode root) {
if(root==null)
return true; //递归出口
boolean left=isValidBST(root.left);
//中
if(pre!=null&&pre.val>=root.val)
return false;
pre=root;
boolean right=isValidBST(root.right);
return left&&right;
}
}
中序遍历的迭代法:
class Solution {
// 迭代
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode pre = null;
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if(cur != null) { //一直把左孩子入栈
stack.push(cur);
cur = cur.left;// 左
}
else
{
// 中,处理
TreeNode node = stack.pop();
if (pre != null && node.val <= pre.val) {
return false;
}
pre = node;
cur = node.right;// 右
}
}
return true;
}
}
530、 二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
对于二叉搜索树,要好好利用二叉搜索树的性质,所以我们需要进行中序遍历即可。
递归法:
class Solution { //因为是中序遍历后是有序的,所以最小值是相邻的
private int result=Integer.MAX_VALUE; //记录差值
TreeNode pre;
public int getMinimumDifference(TreeNode root) {
inorder(root);
return result;
}
private void inorder(TreeNode root)
{
if(root==null) return;
inorder(root.left); //左
if(pre!=null)
result=Math.min(result,root.val-pre.val); //中
pre=root;
inorder(root.right); //右
}
}
非递归的中序遍历:
class Solution {
//中序遍历的非递归法
public int getMinimumDifference(TreeNode root) {
TreeNode pre = null;
Stack<TreeNode> stack =new Stack<>();
TreeNode cur=root; //用来遍历入栈
int result = Integer.MAX_VALUE;
while(cur!=null||!stack.isEmpty())
{
if(cur!=null)
{
stack.push(cur);
cur=cur.left;
}
else
{
cur =stack.pop();
if(pre!=null)
result=Math.min(result,cur.val-pre.val);
pre=cur;
cur=cur.right;
}
}
return result;
}
}
#230、二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
class Solution {
int sum=0;
public int kthSmallest(TreeNode root, int k) {
int result=inorder(root,k);
return result;
}
public int inorder(TreeNode root,int k)
{
if(root==null) return -1;
//左,接住返回值,第k小的元素,有可能在左子树
int left=inorder(root.left,k); // 一直找到最左侧的结点
//每次遍历中结点,sum++
sum++;
if(sum==k)
return root.val;
//右,接住返回值,也有可能在右子树
int right=inorder(root.right,k);
return left==-1 ? right:left;
}
}
501、二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2]
输出:[2]
示例 2:
输入:root = [0]
输出:[0]
暴力法:用一个map统计频率,任何遍历方式都可以。
class Solution {
Map<Integer,Integer> map =new HashMap<>();
public int[] findMode(TreeNode root) {
int sum = Integer.MIN_VALUE;
List<Integer> list =new ArrayList<>();
inorder(root);
Set<Map.Entry<Integer,Integer>> set =map.entrySet();
//先找到最大频率
for(Map.Entry<Integer,Integer> entry : set)
{
if(entry.getValue()>sum)
sum=entry.getValue();
}
//把最大频率的key装入list
for(Map.Entry<Integer,Integer> entry : set)
{
if(entry.getValue()==sum)
list.add(entry.getKey());
}
//把list的元素装入数组返回
int num=0;
int[] result=new int[list.size()];
for(int i=0;i<list.size();i++)
{
result[num++]=list.get(i);
}
return result;
}
private void inorder(TreeNode root)
{
if(root==null) return;
inorder(root.left); //左
map.put(root.val,map.getOrDefault(root.val,0)+1); //中
inorder(root.right); //右
}
}
在二叉搜索树的基础上修改,用pre指针和当前结点判断,相等就把count++,如果conut大于maxcount就把list情空,如果count等于maxcount的话就把val加入list中
class Solution {
TreeNode pre;//记录前一个结点
List<Integer> list =new ArrayList<>();
int maxcount=0;
int count=0;
public int[] findMode(TreeNode root) {
inorder(root);
int[] res =new int[list.size()];
for(int i=0;i<list.size();i++)
{
res[i]=list.get(i);
}
return res;
}
public void inorder(TreeNode root)
{
if(root==null) return;
inorder(root.left); //左
if(pre==null || pre.val!=root.val) //如果是第一个结点或者与前一个结点不同
{
count=1;
}
else if(pre.val==root.val)
{
count++;
}
//更新maxcount
if(count>maxcount)
{
maxcount=count;
list.clear(); //找到了一个更大的 就要把list清空 这样一遍遍历就行
list.add(root.val);
}
else if (count==maxcount)
{
list.add(root.val);
}
pre=root;
inorder(root.right);
}
}
#236、 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。
此题是遍历整棵树,因为在如下代码的后序遍历中,如果想利用left和right做逻辑处理, 不能立刻返回,而是要等left与right逻辑处理完之后才能返回。
后续遍历,不断把找到的p,q向上返回,如果p和q在root的两边,那个p和q最近的公共祖先就是root
class Solution { //即找p,q结点的过程,如果找到了就返回相应的结点
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||root==p||root==q)
return root; //递归一直找到p,q或者空结点
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 left;
else if(right!=null && left==null)
return right;
else //左右都找到了,说明左右都不为空
return root;
}
}
235、二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
此题我们只需要遍历一条边即可。
利用二叉搜索树的有序性,如果q,p在其两边,那么这个结点就是最近的公共祖先。
class Solution {
//如果q,p在其两边,那么这个结点就是最近的公共祖先
//否则就递归
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val>q.val && root.val>p.val) //如果根结点大,则向左递归
return lowestCommonAncestor(root.left,p,q);
if(root.val<q.val && root.val<p.val) //如果根结点小,向右递归
return lowestCommonAncestor(root.right,p,q);
//其他的情况:p,q分别在两边 或者与root相等
return root;
}
}
利用二叉搜索树的迭代法
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root!=null)
{
if(root.val>p.val && root.val>q.val)
root=root.left;
else if(root.val<p.val && root.val<q.val)
root=root.right;
else
return root;
}
return null;
}
}
701、二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
有返回值的话,可以利用返回值完成新加入的节点与其父节点的赋值操作。
//有返回值的递归法,用父节点接住返回值
class Solution {
//实际上就是搜索,在搜索的基础上找到空结点就进行插入!
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root==null) return new TreeNode(val); //为空说明要构造一个结点,进行插入
if(root.val>val)
root.left=insertIntoBST(root.left,val);
else if(root.val<val)
root.right=insertIntoBST(root.right,val);
return root; //返回root,让上一层接住
}
}
在二叉搜索树递归遍历的基础上,用一个pre记录上一个访问的结点,当当前结点为空的时候,把新结点加入!
//没有返回值的递归法,用pre指针记录插入结点的父节点
class Solution {
TreeNode pre;
private void trans(TreeNode cur,int val)
{
//cur==null要放在最前面,引起下面的是cur.val 所以要先判断为空
if(cur==null) //当前结点为空,即找到了插入结点的位置
{
TreeNode node=new TreeNode(val);
if(pre.val>val)
pre.left=node;
else
pre.right=node;
return;
}
pre=cur; //用pre指向即将插入位置的父结点
if(cur.val>val) trans(cur.left,val);
else if(cur.val<val) trans(cur.right,val);
return ;
}
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root==null)
return new TreeNode(val);
trans(root,val);
return root;
}
}
迭代法遍历:循环找到要插入的地方,即空结点
//迭代法:循环找到要插入的地方,即空结点
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
TreeNode pre = root; //记录前一个指针
TreeNode cur = root;
if(root==null)
return new TreeNode(val);
while(cur!=null) //循环一直找到要插入的结点
{
pre=cur;
if(cur.val>val)
{
cur=cur.left;
}
else{
cur=cur.right;
}
}
//当cur为空时,建立一个结点
TreeNode node =new TreeNode(val);
if(pre.val>val)
pre.left=node;
else if(pre.val<val)
pre.right=node;
return root;
}
}
450、删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。
和插入算法一样,也是通过递归的返回结点来进行。
//递归法:返回的是删除过后的子树
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null) return root;
//找到需要删除的结点
if(root.val==key)
{
if(root.left==null)
return root.right;
else if(root.right==null)
return root.left;
else{ //左右子树都不空
TreeNode cur =root.right;
while(cur.left!=null)
{
cur=cur.left; //cur用来找到右子树最左边的结点
}
cur.left=root.left;
root=root.right;
return root;
}
}
if(root.val>key)
root.left=deleteNode(root.left,key);
if(root.val<key) root.right=deleteNode(root.right,key);
return root;
}
}
669、修剪二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
示例 1:
输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]
前序递归法:把不符合条件的子树剔除掉,因为root.val < low时,整个左子树都会小于low,所以递归到右子树就行。即每次剔除一个子树
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
//中 处理逻辑
if (root == null) {
return null;
}
if (root.val < low) { //直接剔除一半
return trimBST(root.right, low, high);
}
if (root.val > high) {
return trimBST(root.left, low, high);
}
// root在[low,high]范围内
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
//返回root
return root; //返回上一级让接收
}
}
#108、将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
删除二叉树节点,增加二叉树节点,都是用递归函数的返回值来完成,这样是比较方便的。
那么本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。
在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组
int mid = (left + right) / 2;,这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了。
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
TreeNode root = traversal(nums, 0, nums.length - 1);
return root;
}
// 左闭右闭区间[left, right]
private TreeNode traversal(int[] nums, int left, int right) {
if (left > right) return null;
int mid = left + ((right - left) >> 1);
TreeNode root = new TreeNode(nums[mid]);
root.left = traversal(nums, left, mid - 1);
root.right = traversal(nums, mid + 1, right);
return root;
}
}
538、把二叉搜索树转换为累加树
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
按照中序遍历的顺序相反
class Solution {
int sum;
public TreeNode convertBST(TreeNode root) {
sum = 0;
convertBST1(root);
return root;
}
// 按右中左顺序遍历,累加即可
public void convertBST1(TreeNode root) {
if (root == null) {
return;
}
convertBST1(root.right); //一直向右递归
sum += root.val; //sum一直记录总和
root.val = sum; //修改本节点的值
convertBST1(root.left);
}
}
总结
二叉树比较重要的点:
1、遍历,基本题目都是涉及到遍历方式,在基本的遍历方式上修改,进行对二叉树的操作。
在二叉树题目选择什么遍历顺序是不少同学头疼的事情,我们做了这么多二叉树的题目了,Carl给大家大体分分类。
- 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
- 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
- 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。
注意在普通二叉树的属性中,我用的是一般为后序,例如单纯求深度就用前序,二叉树:找所有路径 (opens new window)也用了前序,这是为了方便让父节点指向子节点。
所以求普通二叉树的属性还是要具体问题具体分析。
2、递归,一定要熟悉递归三部曲,熟悉递归的方式的书写。