左叶子之和
例题404:给定二叉树的根节点 root ,返回所有左叶子之和。
class Solution {
int res=0;
public int sumOfLeftLeaves(TreeNode root){
Traversal(root);
return res;
}
public void Traversal(TreeNode root){
int lz=0;
if(root.left!=null){//左孩子
Traversal(root.left);
if(root.left.left==null && root.left.right==null){//左叶子
lz=root.left.val;
}
}
if(root.right!=null){
Traversal(root.right); //右
}
res+=lz;//中
}
}
1.就是在遍历的过程中判断左叶子节点,将其值加起来;
2.左叶子不能在当前节点判断,只能在当前节点判断该左孩子不为空,且左孩子无孩子的才是左叶子。
找树左下角的值
例题513:给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
class Solution {
public int findBottomLeftValue(TreeNode root) {
//层序遍历找到最底层第一个元素
int res=levelTraversal(root);
return res;
}
public int levelTraversal(TreeNode root){
Queue<TreeNode> que=new LinkedList<TreeNode>();
int fir,res=0;
//List<List<Integer>> z=new LinkedList<List<Integer>>();
if(root!=null) que.add(root);
while(!que.isEmpty()){
//List<Integer> z1=new LinkedList<>();
int len=que.size();
fir=len;
while(len>0)
{
TreeNode curNode=que.poll();
if(len==fir) res=curNode.val;//记录最后一行第一个元素
if(curNode.left!=null) que.add(curNode.left);
if(curNode.right!=null) que.add(curNode.right);
//z1.add(curNode.val);
len--;
}
//z.add(z1);
}
//return z.get(z.size()-1).get(0);
return res;
}
}
//递归:找到最左边的叶子不一定是最后一层,所以要在递归时计算深度,深度最大的左叶子才是需要的节点。所以递归参数需要节点和深度。
public class findBottomLeftValue {
public static void main(String[] args){
}
}
class Solution {
int maxDepth = INT_MIN;
int result;
int findBottomLeftValue(TreeNode root) {
traversal(root, 0);
return result;
}
void traversal(TreeNode root, int depth) {
if (root.left == NULL && root.right == NULL) {//找到深度最大的叶子节点
if (depth > maxDepth) {
maxDepth = depth;
result = root.val;
}
return;
}
if (root.left!=null) {
depth++;
traversal(root.left, depth);
depth--; // 回溯
}
if (root.right!=null) {
depth++;
traversal(root.right, depth);
depth--; // 回溯
}
return;
}
}
路径总和
例题112:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
找到一条符合要求的路径,所以递归需要返回值,这里的方法用了全局变量,没有使用返回值。但要与下题作比较。
class Solution {
List<Integer> path=new ArrayList<>();
List<Integer> res=new ArrayList<>();//存放每个路径和
public boolean hasPathSum(TreeNode root, int targetSum) {
//递归+回溯
if(root==null) return false;
Traversal(root,path,res);
if(res.indexOf(targetSum)==-1) return false;
else return true;
}
public void Traversal(TreeNode root, List<Integer> path,List<Integer> res){
path.add(root.val);//中
//停止条件
if(root.left==null && root.right==null){//找到叶子节点后进入处理逻辑(将这条路径和加起来)
int sum=0;
for(int i=0;i<path.size();i++){
sum+=path.get(i);
}
res.add(sum);
return;
}
if(root.left!=null){
Traversal(root.left,path,res);
path.remove(path.size()-1);//回溯
}
if(root.right!=null){
Traversal(root.right,path,res);
path.remove(path.size()-1);
}
}
}
1.遇到需要遍历树中所有路径的,都需要用到递归(前中后序都可以,因为中间节点没有处理逻辑)+回溯;
2.java中查找集合中的元素位置函数:indexOf(),如果找到返回下标,否则返回-1表示未找到该元素。
3.也可以把targetSum作为参数传递,每次遍历节点就减去该节点值,直到targetSum变为0且该节点为叶子才true。
4.迭代法可以用一对pair<TreeNode root,int val>来存节点以及其值,在遍历过程中依次加节点值与targetSum比较。
路径总和||
例题113:给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
class Solution {
List<Integer> path=new ArrayList<>();
List<List<Integer>> res=new ArrayList<List<Integer>>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
Traversal(root,path,res,targetSum);
return res;
}
public void Traversal(TreeNode root,List<Integer> path,List<List<Integer>> res,int targetSum){
if(root==null) return;
path.add(root.val);
//停止条件:遇到叶子节点后看该路径和是否符合
if(root.left==null && root.right==null){
int sum=0;
for(int i=0;i<path.size();i++){
sum+=path.get(i);
}
if(sum==targetSum){
//res.add(path);结果错误只有每个集合的第一位
res.add(new ArrayList<>(path));//需要新建集合空间
}
return;
}
if(root.left!=null){
Traversal(root.left,path,res,targetSum);
path.remove(path.size()-1);
}
if(root.right!=null){
Traversal(root.right,path,res,targetSum);
path.remove(path.size()-1);
}
}
}
这道题与上题路径总和类似,都需要用到递归+回溯,但112题不需要遍历完整个树需要返回值,113题需要遍历完整个树所以不需要返回值。这两题学会怎么构造递归
从中序与后序遍历序列构造二叉树
例题106:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
如何根据两个顺序构造一颗唯一的二叉树,原理如下:
根据后序最后一个元素作为切割点(根节点),先切割中序数组,再根据中序数组反过来切割后序数组,一层一层切下去,每次后序数组最后一个元素就是节点元素。
根据以上流程原理,可以写出递归的框架
TreeNode traversal(int[] inorder,int[] postorder){
//第一步
if(postorder.size()==0) return root;
//第二步
int rootValue=postorder[postorder.size()-1];
TreeNode root=new TreeNode(rootValue);
//叶子节点
if(postorder.size()==1) return root;
//第三步:找切割点
for(int delimiterIndex=0;delimiterIndex<inorder.size();delimiterIndex++){
if(rootValue==inorder.get(delimiterIndex)) break;
//第四步:切割中序数组,得到中序左数组和中序右数组
//第五步:切割后序数组,得到后序左数组和后序右数组
//第六步
root.left=traversal(中序左数组,后序左数组);
root.right=traversal(中序右数组,后序右数组);
return root;
}
难点就在于切割,注意切割的标准,是左闭右开、还是左开右闭、左闭右闭,在递归时要保持不变。以左闭右开为例。
//第三步:找切割点
for(int delimiterIndex=0;delimiterIndex<inorder.size();delimiterIndex++){
if(rootValue==inorder.get(delimiterIndex)) break;
//第四步:切割中序数组,得到中序左数组和中序右数组
//中序左数组:[0,delimiterIndex)
int[] leftInorder(inorder.begin(),inorder.begin()+delimiterIndex);
//中序右数组:[delimiterIndex+1,end)
int[] rightInorder(inorder.begin()+delimiterIndex+1,inorder.end());
切割后序数组最后一个元素不需要了,这是切割点也就是二叉树中间的元素。后序数组的切割点怎么找呢?
中序数组和后序数组的大小一定相等,所以后序数组可以按照中序左右数组来切割。
//舍弃后序数组最后一位
postorder.resize(postorder.size()-1);
//左闭右开,使用中序左数组的大小作为切割点:[0,leftInorder.size())
int[] leftPostorder(postorder.begin(),postorder.begin()+leftInorder.size());
//[leftInorder.size(),end)
int[] rightPostorder(postorder.begin()+leftInorder.size(),postorder.end());
此时,中序数组切割成了左中序和右中序数组,后序数组切割成了左后序数组和右后序数组。
接下来,就可以开始递归,代码如下:
root.left=traversal(leftInorder,leftPostorder);
root.right=traversal(rightInorder,rightPostorder);
该题完整代码如下:
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0 || postorder.length==0) return null;
return findNode(inorder,0,inorder.length,postorder,0,postorder.length);
}
public TreeNode findNode(int[] inorder,int inorderStart,int inorderEnd,int[] postorder,int postorderStart,int postorderEnd){
//第一步
//if(postorder.length==0 || inorder.length==0) return null;这样写rootvalue那里要超出数组界限,因为没有限制postorderEnd=0时,postorderEnd-1=-1
if(postorderStart == postorderEnd) return null;//如果后序数组长度为0
//第二步:找到切割点值
int rootValue = postorder[postorderEnd-1];
TreeNode root=new TreeNode(rootValue);
//叶子节点
if(postorderEnd==0) return root;
//第三步:找到切割点在中序数组中的位置
int delimiterIndex;
for(delimiterIndex=inorderStart;delimiterIndex<inorderEnd;delimiterIndex++) {
if (inorder[delimiterIndex]==rootValue) break;
}
//第四步:划分中序左右数组(和c++不同,不能直接初始化int[] leftInorder(inorder.begin(),inorder.begin()+delimiterIndex)
int leftInorderStart=inorderStart;
int leftInorderEnd=delimiterIndex;
int rightInorderStart=delimiterIndex+1;
int rightInorderEnd=inorderEnd;
//第五步:划分后序左右数组
int leftPostorderStart=postorderStart;
int leftPostorderEnd=postorderStart+(delimiterIndex-inorderStart);
int rightPostorderStart=leftPostorderEnd;
int rightPostorderEnd=postorderEnd-1;
//第六步:开始递归
root.left=findNode(inorder,leftInorderStart,leftInorderEnd,postorder,leftPostorderStart,leftPostorderEnd);
root.right=findNode(inorder,rightInorderStart,rightInorderEnd,postorder,rightPostorderStart,rightPostorderEnd);
return root;
}
}
从前序与中序遍历序列构造二叉树
例题105:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
1.先找到前序中的根节点然后分割中序数组,得到左中序和右中序数组,再分割前序数组得到左右前序数组。整个流程与中序和后序构造二叉树类似。
2.递归时掌握好划分的左右区间。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
TreeNode root=new TreeNode();
root=findNode(preorder,0,preorder.length,inorder,0,inorder.length);
return root;
}
public TreeNode findNode(int[] preorder,int preorderStart,int preorderEnd,int[] inorder,int inorderStart,int inorderEnd){
//第一步
if (preorderStart == preorderEnd) return null;
//第二步:找到根节点值
int rootVal=preorder[preorderStart];//不要用0
TreeNode root=new TreeNode(rootVal);
if (preorderEnd - preorderStart == 1) return root;
//第三步:在中序中找到根节点的下标
int pos;
for(pos=inorderStart;pos<inorderEnd;pos++){
if(inorder[pos]==rootVal) break;
}
//第四步:分割中序数组(左开右闭)
int leftInorderStart=inorderStart;
int leftInorderEnd=pos;
int rightInorderStart=pos+1;
int rightInorderEnd=inorderEnd;//不用-1,区间把握好
//第五步:分割前序数组
int leftPreorderStart=preorderStart+1;
int leftPreorderEnd=leftPreorderStart+pos-inorderStart;
int rightPreorderStart=leftPreorderEnd;
int rightPreorderEnd=preorderEnd;//不用-1,区间把握好
//第六步:递归
root.left=findNode(preorder,leftPreorderStart,leftPreorderEnd,inorder,leftInorderStart,leftInorderEnd);
root.right=findNode(preorder,rightPreorderStart,rightPreorderEnd,inorder,rightInorderStart,rightInorderEnd);
return root;
}
}
最大二叉树
例题654:给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。
可通过构造数组来递归,优化为通过下标索引直接在原数组上操作。
每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
需要注意每次递归数组的左右边界,不再是原始数组的头和尾。
确定递归停止条件,如果不写后续内存会溢出。
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
TreeNode root = new TreeNode();
root = traversal(nums, 0, nums.length);
return root;
}
public TreeNode traversal(int[] nums,int left,int right){
if(left>=right) return null;//必须有停止条件,否则后续会溢出
int mx=nums[left];
int pos=0;
for(int i=left;i<right;i++){
if(nums[i]>=mx){//注意这里i从left开始,如果left就是最大的需要加=,不然会出错
mx=nums[i];
pos=i;
}
}
TreeNode root=new TreeNode(mx);
//左开右闭,允许空节点进入递归,停止条件为遇到空节点即数组区间为0
//如果不让空节点进入递归,则停止条件为遇到叶子节点
root.left=traversal(nums,left,pos);//不能写0
root.right=traversal(nums,pos+1,right);//不能写nums.length,应该是每次开始的数组边界
return root;
}
}
二叉树周末总结
1.递归中隐藏着回溯
2.判断左叶子,需要相连的三层之间构成约束条件,通过节点的父节点以及孩子结点确定本节点的属性。
3.二叉树的高度与深度的不同。
4.递归函数返回值什么时候需要,什么时候不需要?如果需要搜索整个二叉树则不需要返回值,如果要搜索一条符合条件的路径就需要返回值,因为遇到符合条件的路径了就要及时返回。
5.构造二叉树:前序与中序、中序与后序都能构造唯一的一棵二叉树。
几个注意点:
- 分割的时候,区间不变原则,左开右闭或者左闭右闭。
- 2.分割的时候注意前序后者后序已经有一个节点作为中间节点了,后序不再使用。
- 3.如何使用切割后的前后序数组来切割中序数组?利用切割后的左右数组长度来确定,因为中序和后序数组大小相同。
- 4.前序与后序无法确定唯一的一棵二叉树,因为没有中序遍历无法确定左右部分,也就无法分割。
6.构造二叉树时,尽量不要定义新的数组,而是通过下标索引直接在原数组上进行操作,节省时间与空间开销。递归函数什么时候加if,什么时候不加if,其实就是控制空节点是否进入递归。如果让空节点进入递归,就不加if,如果不让空节点进入递归,就加if限制,相应的终止条件也会发生变化。