二叉树整理2

本文详细解析了关于二叉树的四个核心算法问题:最小深度、判断平衡、子树判断以及路径总和。通过递归和迭代方法实现,涉及二叉树的特性和遍历策略。对于每个问题,提供了清晰的思路和代码实现,有助于深入理解二叉树的操作。
摘要由CSDN通过智能技术生成

1.求二叉树的最小深度

首先明白最小深度的概念:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
在这里插入图片描述
如果这么求的话,没有左孩子的分支会算为最短深度。
所以,如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。

class Solution {
public:
//递归法
    int getdepth(TreeNode* cur)
    {
        if(cur==NULL) return 0;
        int minleft=getdepth(cur->left);
        int minright=getdepth(cur->right);
        if(cur->left && cur->right)
        {
             return min(minleft,minright)+1;
        }
        else if(!cur->left && cur->right)
        {
             return minright+1;
        }
        return minleft+1;
     
    }
    int minDepth(TreeNode* root) {
     if(root==NULL) return 0;
     return getdepth(root);

    }
};

2、给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
在这里插入图片描述
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
这里强调一波概念:
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

在这里插入图片描述

class Solution {
public:
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
    int  height(TreeNode* cur)
    {
        if(cur==NULL)  return 0;
        int lheight=height(cur->left);
        int rheight=height(cur->right);
        if(lheight==-1 || rheight==-1 ||(abs(lheight-rheight)>1))   return -1;
        //return 0; 一定要返回高度啊,不返回高度怎么计算差值大于1
        return 1+max(lheight,rheight);
    }
    bool isBalanced(TreeNode* root) {
          // int res=height(root);
        //if (res==-1)
       // {
       //     return false;
        //}
        //return true ;
        //代码精简之后
      return getDepth(root) == -1 ? false : true;
        
    }
};

3.判断子树 vs 判断相等

要判断一个树 t 是不是树 s 的子树,那么可以判断 t 是否和树 s 的任意子树相等。那么就转化成 100. Same Tree。
即,这个题的做法就是在 s 的每个子节点上,判断该子节点是否和 t 相等。

判断两个树是否相等的三个条件是与的关系,即:

当前两个树的根节点值相等;
并且,s 的左子树和 t 的左子树相等;
并且,s 的右子树和 t 的右子树相等。
而判断 t 是否为 s 的子树的三个条件是或的关系,即:

当前两棵树相等;
或者,t 是 s 的左子树或左子树的子树;
或者,t 是 s 的右子树或右子树的子树。
**判断相等:

class Solution {
public:
    bool issame(TreeNode* cur1, TreeNode* cur2)
    {
        if(cur1==NULL && cur2==NULL) return true;
        if(cur1==NULL && cur2!=NULL)  return false;
        if(cur1!=NULL && cur2==NULL)  return false;
        bool l=issame(cur1->left,cur2->left);
        bool r=issame(cur1->right,cur2->right);
        if(cur1->val==cur2->val && l && r)
        return true;
        return false;
    }
    bool isSameTree(TreeNode* p, TreeNode* q) {
       return  issame(p,q);
    }
};

判断子树:

`class Solution {
public:
    bool issame(TreeNode* cur1, TreeNode* cur2)
    {
        if(cur1==NULL && cur2==NULL) return true;
        if(cur1==NULL && cur2!=NULL)  return false;
        if(cur1!=NULL && cur2==NULL)  return false;
        bool l=issame(cur1->left,cur2->left);
        bool r=issame(cur1->right,cur2->right);
        if(cur1->val==cur2->val && l && r)
        return true;
        return false;
     }
        bool isSubtree(TreeNode* root, TreeNode* subRoot) {
        if(!root && !subRoot)   return true; // 两根节点都为空,返回相同
        if((root&&!subRoot) || (!root&&subRoot))    return false; // 两根节点只有一个为空,返回不同
        bool ans= issame(root,subRoot) ||  isSubtree(root->left,subRoot)  || isSubtree(root->right,subRoot);
         return  ans;
    }
};

`**

4.构造二叉树

4.1 力扣106.从中序与后序遍历序列构造二叉树
在这里插入图片描述
说到一层一层切割,就应该想到了递归。
来看一下一共分几步:
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,
后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。
此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。
中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。

class Solution {
public:
//p为中序遍历,q为后序遍历
    TreeNode* traversal(vector<int>& p, vector<int>& q)
    {
        //1.若数组大小为零,则为空树
        if(p.size()==0 || q.size()==0)   return NULL;  
         
        //2.后序遍历的最后一个数为为根结点,也可以此切割中序遍历
        int rootval=q[q.size()-1];
        TreeNode* root=new TreeNode(rootval);// 
        //判断是否为叶子结点
         if (q.size() == 1) return root;
        //非叶子结点    
        int delimiterindex;  
        for( delimiterindex=0;delimiterindex<p.size();delimiterindex++ )
        {
            if(p[delimiterindex]==rootval)
            break;
        }
        
        //注意:vector<int> v2(v1.begin(),v1.end())
        //将[v1.begin(),v1.end())区间元素拷贝给v2
        //v1.end()指的是v1最后一个元素的下一个元素
        //3.切割中序数组
        vector<int> pl(p.begin(),p.begin()+delimiterindex);
        vector<int> pr(p.begin()+delimiterindex+1,p.end()); 
        
        //4.切割后序数组(后序数组与中序数组左右区间个数相同,舍弃最后一个数)
        //法1:
        // q.resize( q.size()-1 );
        //vector<int> ql(q.begin(),q.begin()+pl.size());
        //vector<int> qr(q.begin()+pl.size(),q.end()); 
        //法2:
        vector<int> ql(q.begin(),q.begin()+pl.size());
        vector<int> qr(q.begin()+pl.size(),q.end()-1);

        //5.递归处理中序、后序的左右数组
        root->left=traversal(pl,ql);
        root->right=traversal(pr,qr);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        //if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        return traversal(inorder,postorder);
    }
};

4.2 力扣105.从前序与中序遍历序列构造二叉树

class Solution {
public:
//p为中序遍历,q为前序遍历
    TreeNode* traversal(vector<int>& p, vector<int>& q)
    {
        //1.若数组大小为零,则为空树
        if(p.size()==0 || q.size()==0)   return NULL;   
        //2.前序遍历的第一个数为为根结点,也可以此切割中序遍历
        int rootval=q[0];
        TreeNode* root=new TreeNode(rootval);// 
        //判断是否为叶子结点
         if (q.size() == 1) return root;
        //非叶子结点    
        int delimiterindex;  
        for( delimiterindex=0;delimiterindex<p.size();delimiterindex++ )
        {
            if(p[delimiterindex]==rootval)
            break;
        }
        //注意:vector<int> v2(v1.begin(),v1.end())
        //将[v1.begin(),v1.end())区间元素拷贝给v2
        //v1.end()指的是v1最后一个元素的下一个元素
        //3.切割中序数组[0,delimiterindex)
        vector<int> pl(p.begin(),p.begin()+delimiterindex);
        vector<int> pr(p.begin()+delimiterindex+1,p.end()); 


        //4.切割前序数组(前序数组与中序数组左右区间个数相同)
        //舍弃第一个元素
        vector<int> ql(q.begin()+1,q.begin()+1+pl.size());
        vector<int> qr(q.begin()+1+pl.size(),q.end()); 

        //5.递归处理中序、后序的左右数组
        root->left=traversal(pl,ql);
        root->right=traversal(pr,qr);
        return root;
    }
    
      

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
     return traversal(inorder,preorder);
    }
};

5.路径总和

5.1给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和
1.确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
如果需要搜索整颗二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)

如果需要搜索整颗二叉树且需要处理递归返回值,递归函数就需要返回值。(这种情况我们在236. 二叉树的最近公共祖先介绍)

如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?
在这里插入图片描述
在这里插入图片描述
图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
2、确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。

迭代:
class Solution {
public:  
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==NULL) return false;
        //pair结构来存放这个栈里的元素
        //创建方式1:pair<type, type> p(value1,value2);
        //创建方式2:pair<type, type> p=make_pair(value1,value2);       
        //pair<treenode*, int> pair<节点指针,路径数值>
        stack<pair<TreeNode*, int>> st;
        //用pair结构存放栈里的数组
        pair<TreeNode*, int> p (root,root->val);
        st.push(p);
        while(!st.empty())
        {
            pair<TreeNode*, int> node=st.top();
            st.pop();
            if(node.first->left==NULL && node.first->right==NULL && node.second==targetSum )
            return true;
            if(node.first->right)
            { 
                st.push( pair<TreeNode*, int>  (node.first->right, node.second+node.first->right->val));
            }
             if(node.first->left)
            { 
                st.push( pair<TreeNode*, int>  (node.first->left, node.second+node.first->left->val));
            }
        }
        return false;
    }
};
递归:
class Solution {
public:
    bool traversal(TreeNode* cur, int t)
    {
        //终止条件:叶子结点且值为0
        if(!cur->left && !cur->right && t==0)
        {
            return true;
        }
        if(!cur->left && !cur->right && t!=0)
        return false;
        if(cur->left)
        {
            t-=cur->left->val;
            if(traversal(cur->left,t)) return true;
            t+=cur->left->val;
        }
        if(cur->right)
        {
            t-=cur->right->val;
            if(traversal(cur->right,t)) return true;
            t+=cur->right->val;
        }  
        //精简版
        if (cur->left) 
        { 	// 左 (空节点不遍历)
    		// 遇到叶子节点返回true,则直接返回true
    		if (traversal(cur->left, count - cur->left->val)) 
    		return true; // 注意这里有回溯的逻辑
		}
		if (cur->right)
		{ // 右 (空节点不遍历)
    	// 遇到叶子节点返回true,则直接返回true
    		if (traversal(cur->right, count - cur->right->val))		
    		 return true; // 注意这里有回溯的逻辑
			}
        //如果都没有一条路径符合,就返回false;
        return false;         
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==NULL) return false;
        return traversal(root,targetSum-root->val);
    }
};

113、路径总和
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
在这里插入图片描述
输入: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 {
private:
    vector<vector<int>> res;
    vector<int> path;//每一条path都记录下来
    //只有符合条件的才放进res
    void traversal(TreeNode* cur, int t)
    {
        if(!cur->left && !cur->right && t==0)
        {
            res.push_back(path);
            return;
        }
        if(!cur->left && !cur->right)
        return;
        if(cur->left)
        {
            path.push_back(cur->left->val);
            t-=cur->left->val;
            traversal(cur->left,t);
            t+=cur->left->val;
            path.pop_back();
        }
        if(cur->right)
        {
            path.push_back(cur->right->val);
            t-=cur->right->val;
            traversal(cur->right,t);
            t+=cur->right->val;
            path.pop_back();
        }
        return;
    }
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        res.clear();
        path.clear();
        if(root==NULL)
        {
        return res;
        }
        path.push_back(root->val); // 把根节点放进路径
        traversal(root,targetSum-root->val);
        return res;
   
    }
};

6.搜索二叉树

二叉搜索树是一个有序树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉搜索树
700题:给定二叉搜索树(BST)的根节点和一个值。你需要在BST中找到节点值等于给定值的节点。返回以该节点为根的子树。如果节点不存在,则返回 NULL。

class Solution {
public:
/*
     TreeNode* traversal(TreeNode* cur, int val)
     {
         if(cur==NULL) return NULL;
         else if(cur->val==val) 
         return cur;
         else if(cur->val>val)
         {
             return traversal( cur->left,val);
         }

            return  traversal( cur->right,val);

     }
    TreeNode* searchBST(TreeNode* root, int val) {
            return traversal(root,val);
 */
    TreeNode* searchBST(TreeNode* root, int val) {
                while(root!=NULL)
                {
                    if(root->val>val)
                    root=root->left;
                    else if(root->val<val)
                    root=root->right;
                    else 
                    return root;
                }
                return NULL;
    }
};

7.验证搜索二叉树

因为搜索二叉树有序排列,所以可按中序输出数组后判断是否有序
陷阱1
不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
在这里插入图片描述
节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了!
在二叉树的中序遍历 讲过按中序遍历二叉树时的特点,在下图中使用中序遍历对二叉树进行遍历时,对节点的访问顺序为
a1 -> a -> a2 -> b -> c1 -> c -> c2
在这里插入图片描述

如果是二叉搜索树,那么对节点的访问顺序为应该为单调递增的,及
a1 < a < a2 < b < c1 < c < c2
我们可以利用这个特点,增加一个last指针用来保存上一个访问的节点来与当前节点进行判断

如果当前节点大于前一个节点,说明满足二叉搜索树的定义,继续递归判断
否则直接返回false

class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;
        bool left = isValidBST(root->left);

        if (pre != NULL && pre->val >= root->val) return false;
        pre = root; // 记录前一个节点

        bool right = isValidBST(root->right);
        return left && right;
    }
};

class Solution {
private:
    vector<int> vec;
    void traversal(TreeNode* root) {
        if (root == NULL) return;
        traversal(root->left);
        vec.push_back(root->val); // 将二叉搜索树转换为有序数组
        traversal(root->right);
    }
public:
    bool isValidBST(TreeNode* root) {
        vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
        traversal(root);
        for (int i = 1; i < vec.size(); i++) {
            // 注意要小于等于,搜索树里不能有相同元素
            if (vec[i] <= vec[i - 1]) return false;
        }
        return true;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值