【C++编程能力提升】

一、513 找树左下角的值

题目链接:513 找树左下角的值

核心:明确要求解的是最底层最左边的节点值(可能在左子树也可能在右子树)
(1)层序遍历:较为简单,只需记录最后一层的第一个节点值(实现时是记录每一层的第一个节点值,但下一层会覆盖前一层的节点值,因此最终记录的是最后一层的第一个节点值);
(2)递归法:最底层即最大深度所在层的最左边节点值,明确深度最大的叶子节点一定是最后一行,以及优先左边搜索时遇到的叶子节点即最左边的值,故使用前序遍历计算二叉树的深度。
第一,确定递归函数的参数和返回值:参数是树的根节点和记录最大深度的int变量;返回值为void,因为无需返回值;(主函数需返回最左边节点值,因此设置2个全局变量,其一记录最大深度,其二记录最大深度时最左边节点值;
第二,确定终止条件:遇到叶子节点则更新最大深度,并记录最大深度时的叶子节点值;
第三,确定单层递归的逻辑:递归时需使用回溯,即每次递归前需对深度加1,递归后需要回溯至上一层,故对深度减1.

    int maxDepth=INT_MIN;   //全局变量,记录二叉树的最大深度
    int res=0;              //全局变量,记录最大深度最左边的节点值
    void getDepth(TreeNode* node,int depth)
    {//前序遍历:中左右
        if(!node->left && !node->right)
        {//叶子节点【终止条件】
            if(depth>maxDepth)
            {
                maxDepth=depth; //更新最大深度
                res=node->val;  //更新最大深度时的叶子节点值
            }
            return; //理解为何此处需要return?实际递归函数没有return也正确,为什么?
        }
        //【中】不存在处理
        if(node->left)
        {//左   
            depth++;
            getDepth(node->left,depth);
            depth--;    //回溯至上一个节点
        }
        if(node->right)
        {
            depth++;
            getDepth(node->right,depth);
            depth--;    //回溯至上一个节点
        }
    }
    int findBottomLeftValue(TreeNode* root) {
    //递归法:利用前序遍历求解二叉树最大深度
        getDepth(root,1);   //调用递归函数,根节点的深度为1
        return res;         

    /*
    //层序遍历:记录最后一层的第一个节点值
    queue<TreeNode*> que;
    int res=0;
    if(root)
        que.push(root);
    while(!que.empty())
    {
        int size=que.size();
        for(int i=0;i<size;++i)
        {//逐层遍历
            TreeNode* node=que.front(); //队头元素
            que.pop();
            if(i==0)   //第一个元素,记录第二层时会覆盖第一层,最终保留最后一层的第一个元素
                res=node->val;  
            if(node->left)
                que.push(node->left);
            if(node->right)
                que.push(node->right);
        }
    }
    return res;
    */
    }

二、112 路径总和

题目链接:112 路径总和

核心:递归+回溯,判断是否存在满足路径之和为targetSum的一条路径
(1)递归:(前序遍历,对中无需处理)
第一,明确递归函数的参数和返回值:参数是树的根节点和路径之和sum,要求返回是否存在,故返回值是bool;
第二,明确终止条件:当前节点为叶子节点,且当前路径和sum被减至0(每经过一个节点,则将初始路径和sum减去该节点值,若存在一条路径满足条件,则此时sum=0);否则,即当前节点是叶子节点但sum不为0,则说明此条路径不符合条件。
第三,明确单层递归的逻辑:递归左子树或右子树时,sum需减去当前节点值,然后进行递归处理,若调用的递归函数返回true表示此路径满足条件,再次返回true,否则回溯至上一层,sum需加上当前节点值;
注意:递归函数的最后一次处理是返回false,如何理解?

    bool traversal(TreeNode* node,int sum)
    {//递归函数:返回true、false,sum是初始路径和,每次减去节点值,若最后为0且当前节点是叶子节点,说明满足条件
        if(!node->left && !node->right && sum==0)
            return true;    //叶子节点且当前路径和被减至0
        else if(!node->left && !node->right)
            return false;   //叶子节点但当前路径和不为0
        if(node->left)
        {//左
            sum-=node->left->val;   //递归,处理节点
            if(traversal(node->left,sum))
                return true;     //这一步不太理解!
            sum+=node->left->val;    //回溯
        }
        if(node->right)
        {//右
            sum-=node->right->val;
            if(traversal(node->right,sum))
                return true;
            sum+=node->right->val;  //回溯
        }
        return false;   //必须有此返回,为什么?
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
    //递归法:使用前序遍历,注意回溯
        if(!root)
            return false;   //根节点为空时不可调用递归函数
        return traversal(root,targetSum-root->val);

    /*
    //迭代法:使用栈模拟前序遍历
    stack<pair<TreeNode*,int>> stk; //使用pair记录节点和路径之和
    if(!root)
        return false;
    stk.push(pair<TreeNode*,int>(root,root->val));
    while(!stk.empty())
    {
        pair<TreeNode*,int> node=stk.top(); //栈顶元素
        stk.pop();
        if(!node.first->left && !node.first->right && targetSum==node.second)
            return true;    //遇到叶子节点且当前节点路径之和与给定sum相等,则符合要求
        if(node.first->right)   //先压入right,在栈中弹出才是先left后right
            stk.push(pair<TreeNode*,int>(node.first->right,node.second+node.first->right->val)); //入栈的是当前节点的右孩子,以及当前节点值与当前节点右孩子值这条路径之和
        if(node.first->left)
            stk.push(pair<TreeNode*,int>(node.first->left,node.second+node.first->left->val));
    }
    return false;   //遍历二叉树所有节点都没有找到一条路径之和为targetSum
    */
    }

扩展: 113 路径总和II

题目链接:113 路径总和II

核心:与112的思想相同,但区别是需要返回满足条件的路径组合
定义2个全局变量,其一是记录满足条件的所有路径(二维数组),其一是记录某一条满足条件的路径。

第一,明确递归函数的参数和返回值:参数是树的根节点和路径目标和sum,要返回的路劲用全局变量声明,因此递归函数无需返回任何变量;
第二,明确终止条件:当前节点为叶子节点且sum为0(每经过一条路径的节点sum都需要减去当前节点值),则该条路经(从根节点开始由各节点组成的数组)是满足条件的,需要记录到二维数组的所有路径中,并return;否则,即当前节点为叶子节点但sum不为0,说明该条路径不满足条件需要立即return;
第三,明确单层递归的逻辑:在递归处理左、右子树时,先将节点值push到当前路径,并在sum上减去当前节点值,然后调用递归函数记录是否存在满足条件的路径,最后需要回溯至上一层节点,即sum需加上当前节点值,并将当前节点从当前路径中弹出。
注意:对递归函数中三处return的含义理解不够清晰。

   vector<vector<int>> res;    //记录所有满足条件的路径
    vector<int> path;           //记录满足条件的一条路径
    void traversal(TreeNode* node,int sum)
    {
        if(!node->left && !node->right && sum==0)
        {//叶子节点且路径之和满足条件
            res.push_back(path);    //当前节点所在的路径push到res,并返回
            return;
        }
        if(!node->left && !node->right)
            return; //不满足条件直接返回
        if(node->left)
        {//左
            path.push_back(node->left->val);    //左孩子节点值加入到此路径
            sum-=node->left->val;   
            traversal(node->left,sum);
            sum+=node->left->val;   //回溯
            path.pop_back();        //回溯,将当前节点左孩子弹出
        }
        if(node->right)
        {//右
            path.push_back(node->right->val);
            sum-=node->right->val;
            traversal(node->right,sum);
            sum+=node->right->val;  //回溯
            path.pop_back();        //回溯
        }
        return; //如何理解此处的return?
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
    //递归法:
        res.clear();
        path.clear();
        if(!root)
            return res;
        path.push_back(root->val);  //root不为空时需记录此节点
        traversal(root,targetSum-root->val);    //调用递归函数
        return res;
    }

三、106 从中序与后序遍历构造二叉树

题目链接:106 从中序和后序遍历构造二叉树

核心:明确利用中序和后序遍历构造的顺序,由后序遍历的最后一个节点确定每一层的root,然后利用该root对中序遍历分成左子树和右子树,进而对后序遍历分成左子树和右子树(除了此时的root),分别递归处理左、右子树,直到root为叶子节点
第一,明确递归函数的参数和返回值:参数是中序和后序遍历数组,返回值是根节点root,类型是TreeNode*;
第二,明确递归的终止条件:后序遍历数组为空时说明根节点为空;
第三,明确单层递归逻辑:
(1)获取后序遍历数组的最后一个节点,即当前层的root,并且判断此时root是否为叶子节点;
(2)由root节点值确定中序遍历数组的切割点;
(3)切割中序数组,得到左中序数组和右中序数组;
(4)切割后序数组,得到左后序数组和右后序数组,每次切割前需去除后序数组的最后一个节点,且后序数组切割后得到的左、右后序数组大小应与左、右中序数组大小相同,因此可根据左中序数组的长度来切割后序数组;
(5)递归处理左子树和右子树。
注意:切割时一定要保证循环不变量,即统一使用左闭右开或左闭右闭。

   TreeNode* traversal(vector<int>& inorder,vector<int>& postorder)
    {//递归函数:传入参数是已知的中、后序遍历数组,返回值是构造的二叉树,即root
        if(postorder.size()==0)
            return nullptr; //1. 后序遍历数组为空说明当前二叉树为空
        //2. 获取后序遍历的最后一个节点,即root
        int rootvalue=postorder[postorder.size()-1];    
        TreeNode* root=new TreeNode(rootvalue); //定义一个根节点并初始化
        //3.该root可能为叶子节点,即无左右孩子,无需递归
        if(postorder.size()==1) 
            return root;
        //4.找到中序遍历的切割点(rootvalue)
        int cutpoint;
        for(cutpoint=0;cutpoint<inorder.size();++cutpoint)
        {
            if(inorder[cutpoint]==rootvalue)
                break;  
        }
        //5. 切割中序遍历数组,得到左中序和右中序
        //left:[0,cutpoint),right:[cutpoint+1,end)
        vector<int> leftInorder(inorder.begin(),inorder.begin()+cutpoint);
        vector<int> rightInorder(inorder.begin()+cutpoint+1,inorder.end());

        postorder.resize(postorder.size()-1);   //去除后序遍历数组的最后一个元素,即root

        //6. 根据左中序数组长度切割后序遍历数组
        //left:[0,leftInorder.size()),right:[leftInorder.size(),end)
        vector<int> leftPostorder(postorder.begin(),postorder.begin()+leftInorder.size());
        vector<int> rightPostorder(postorder.begin()+leftInorder.size(),postorder.end());
        
        //7. 递归处理左中序和左后序,右中序和右后序
        root->left=traversal(leftInorder,leftPostorder);
        root->right=traversal(rightInorder,rightPostorder);

        return root;    //返回根节点
    }

    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        //递归法:需定义一个递归函数
        if(inorder.size()==0 || postorder.size()==0)
            return NULL;
        return traversal(inorder,postorder);
    }

扩展:105 从前序和中序遍历构造二叉树

题目连接:105 从前序和中序遍历构造二叉树

核心:前序找根,中序分左右!二叉树遍历问题大概率考虑递归思想!!
第一,前序遍历的第一个元素一定是整个二叉树的根节点root;
第二,由根节点root在中序遍历的位置可以划分中序遍历左右子树;
第三,若要递归除了中序遍历的左右子树,仍需要前序遍历的左右子树,而左右子树的size不论什么遍历都是相同的,由此可确定前序遍历的左右子树;
第四,递归处理左右子树,确定二叉树每一层的根节点和其左右节点。

Q:何时结束递归?
A:左右子树的size=1时,即左右子树只有root,此时无需递归,直接return该root。

    TreeNode* traversal(vector<int>& preorder,vector<int>& inorder)
    {//递归函数,与106思想类似
        if(preorder.size()==0)
            return nullptr;     //1. 前序遍历数组为空表示二叉树为空
        //2. 获取前序遍历的第一个节点,即root
        int rootvalue=preorder[0];
        TreeNode* root=new TreeNode(rootvalue);
        //3. 该root可能为叶子节点,无需递归处理
        if(preorder.size()==1)
            return root;
        //4.确定中序遍历的切割点
        int cutpoint;
        for(cutpoint=0;cutpoint<inorder.size();++cutpoint)
        {
            if(inorder[cutpoint]==rootvalue)
                break;
        }
        //5.切割中序遍历数组得到左、右中序
        //left:[0,cutpoint),right:[cutpoint+1,end)
        vector<int> leftInorder(inorder.begin(),inorder.begin()+cutpoint);
        vector<int> rightInorder(inorder.begin()+cutpoint+1,inorder.end());

        //6.切割前序遍历数组
        //left:[1,1+leftInorder.size()),right:[1+leftInorder.size(),end)
        //注意:前序遍历的第一个元素总是root,即已被使用,需跳过
        vector<int> leftPreorder(preorder.begin()+1,preorder.begin()+1+leftInorder.size());
        vector<int> rightPreorder(preorder.begin()+1+leftInorder.size(),preorder.end());
        //7.递归处理
        root->left=traversal(leftPreorder,leftInorder);
        root->right=traversal(rightPreorder,rightInorder);

        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0 || inorder.size()==0)
            return nullptr;
        return traversal(preorder,inorder);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值