C++学习笔记—二叉树(3)

资料来源:代码随想录

15.左叶子之和 404

无法直接判断某个节点是不是左叶子,只能判断是不是叶子,必须要通过该节点的父节点来判断其左孩子是不是左叶子。

递归法:

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        //终止条件:
        if(root==NULL) return 0;   //空节点左叶子必为0
        if(root->left==NULL && root->right==NULL) return 0; //已经访问到叶子节点了,叶子节点没有孩子,所以其左叶子必为0

        //单层递归逻辑
        int leftValue=sumOfLeftLeaves(root->left);  //递归左节点,这里是把根节点的左孩子当做一个新的根节点传入,求根节点的左子树的左叶子之和
        //求值逻辑
        if(root->left!=NULL && root->left->left==NULL && root->left->right==NULL)  //根节点的左节点不为空,但左节点的左右孩子均为空,说明这个左节点是左叶子节点,记录一下
        {
            leftValue=root->left->val;
        }
        int rightValue=sumOfLeftLeaves(root->right);  //递归右节点,这里是把根节点右孩子当做一个新的节点传入,求值逻辑使用上面左节点的那个就可以

        int sum=leftValue+rightValue;  //总左叶子之和=左子树左叶子之和+右子树左叶子之和
        return sum;
    }
};

迭代法:

和二叉树的迭代遍历是一样的,只要在遍历的过程中把左叶子节点统计出来并累加起来就可以了。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        if(root==NULL) return 0;
        st.push(root);
        int result=0;

        while(!st.empty())
        {
            TreeNode* node=st.top();
            st.pop();
            //统计左叶子节点并累加
            if(node->left!=NULL && node->left->left==NULL && node->left->right==NULL)
            {
                result=result+node->left->val;
            }
            //注意顺序:
            if(node->right) st.push(node->right);
            if(node->left) st.push(node->left);
        }

        return result;
    }
};

16.找树左下角的值 513

递归:

一直向左遍历是不对的,因为最左边的值未必在最后一行,要首先保证是最后一行,然后再找最左边的。

思路:寻找深度最大的叶子节点,优先向左遍历,以此找到最后一行最左的值。前中后序都可以,因为本题没有中间的处理逻辑,所以保证左优先即可。

class Solution {
public:

    //定义两个全局变量,这样接下来的递归函数修改的就是全局变量,那么就不需要返回值了
    int maxDepth=INT_MIN; //最大深度
    int result;   //存在树左下角的值

    //递归
    void traversal(TreeNode* root, int depth)   //传入一个变量用来记录深度
    {
        //终止条件:遍历到叶子节点就更新最大深度,并记录数值
        if(root->left==NULL && root->right==NULL)
        {
            if(maxDepth<depth)
            {
                maxDepth=depth;
                result=root->val;
            }
            return;
        }
        

        //单层递归逻辑,先左后右
        if(root->left)
        {
            depth++;
            traversal(root->left,depth);
            depth--;  //回溯
        }
        if(root->right)
        {
            depth++;
            traversal(root->right,depth);
            depth--;
        }

        return;
    }

    int findBottomLeftValue(TreeNode* root) {
        traversal(root,0);
        return result;  //因为是全局变量,所以在这可以直接返回
    }
};

要回溯是因为深度必须-1,返回到上一层节点,才能继续往另一边遍历。

回溯可以精简一下:递归时传入的深度是depth+1,等于没有改变depth这个变量的值,也就不需要+1再-1了。

        if(root->left)
        {
            traversal(root->left,depth+1);
        }
        if(root->right)
        {
            traversal(root->right,depth+1);
        }

迭代:

层序遍历,记录最后一层第一个节点数值即可。

从左向右遍历,每一层的第一个节点必是该层的最左节点。如何实现“最后一层”?每迭代一层,结果更新一次,最后返回的就是最后一层。

class Solution {
public:

    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que;
        int result=0;

        if(root!=NULL) 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)  //记录每一层的第一个节点数值
                {
                    result=node->val;
                }
                if(node->left) que.push(node->left);  //从左到右遍历
                if(node->right) que.push(node->right);
            }
        }
        return result;
    }
};

17.路径总和  112

递归:

1.函数参数及返回值:

参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

2.终止条件

计数器如何统计这一条路径的和呢?

不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值

如果最后count == 0,同时到了叶子节点的话,说明找到了目标和

如果遍历到了叶子节点,count不为0,就是没找到。

3.单层递归逻辑

首先判断节点是否为空,不要让空节点进入递归。

以左节点为例。目的是找传入的节点的左子树中是否有符合条件的路径,往下走一步就要用count减掉这一个节点的值。递归左子树也是会有返回值的,可以直接对这个返回值进行处理,如果这个返回值为真的话,说明在左子树中找到了符合条件的路径,那么整体就可以直接返回了,所以return TRUE。递归结束后,为了能够后撤并重新开始,需要回溯,把减掉的值再加上。

class Solution {
public:
    //递归函数
    bool traversal(TreeNode* cur, int count)  //传入的参数包括一个二叉树根节点和计数器,因为要搜索一条符合条件的路径,找到符合条件的要立即返回,所以有返回值
    {
        //终止条件:
        //到叶子节点了,且计数器减到0,则说明找到了符合要求的路径;计数器没有减到0,说明没找到
        if(cur->left==NULL && cur->right==NULL && count==0) return true;
        if(cur->left==NULL && cur->right==NULL && count!=0) return false;

        //单层递归逻辑:没有中间处理节点,所以前中后序都可以
        if(cur->left)
        {
            count=count-cur->left->val;
            if(traversal(cur->left,count)) return true;
            count=count+cur->left->val;  //回溯,后撤,以继续找符合条件的路径
        }
        if(cur->right)
        {
            count=count-cur->right->val;
            if(traversal(cur->right,count)) return true;
            count=count+cur->right->val;
        }
        return false;  //以上都不成立,说明左右都没找到
    }

    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==NULL) return false;
        bool result=traversal(root,targetSum-root->val);
        return result;
    }
};

回溯的过程还可以精简一下,让count的值不要随着递归被改变就行,所以直接传入减过的参数。

        if(cur->left)
        {
            if(traversal(cur->left,count-cur->left->val)) return true;

        }
        if(cur->right)
        {
            if(traversal(cur->right,count-cur->right->val)) return true;
        }

迭代:

如果使用栈模拟递归的话。

此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。

c++就用pair结构来存放这个栈里的元素。

定义为:pair<TreeNode*, int> pair<节点指针,路径数值总和>

这个为栈里的一个元素。

class Solution {
public:
    
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root==NULL) return false;
        //栈内存放元素:
        stack<pair<TreeNode*, int>> st;   //pair<节点指针,路径节点数值之和>
        st.push(pair<TreeNode*,int>(root,root->val));

        while(!st.empty())  //栈迭代不需要size
        {
            pair<TreeNode*,int> node=st.top();
            st.pop();

            //到达叶子节点,且节点数值之和为sum
            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;
    }
};

113 路径总和

要找到符合条件的所有路径,所以要遍历整棵二叉树,则不需要返回值

class Solution {
private:
    //因为要遍历整棵树,所以递归函数没有返回值,那么最终需要的结果就应该被定义为全局变量
    vector<vector<int>> result;  //要存好几条路径,所以结果是二维数组 
    vector<int> path;  //单条路径

    //递归函数,没有返回值
    //思路也是从要求的目标值开始减,在叶子节点减到0说明找到了
    void traversal(TreeNode* cur, int count)
    {
        //终止条件:找到和没找到两种,都返回
        if(cur->left==NULL && cur->right==NULL && count==0)
        {
            result.push_back(path);  //找到了,把这条路径存起来
            return;
        }
        if(cur->left==NULL && cur->right==NULL)  //到了叶子节点但count没有到0,说明没找到,直接返回
        {
            return;
        }

        //单层递归逻辑,只有左右
        if(cur->left)  //空节点不进递归
        {
            path.push_back(cur->left->val);  //把节点放进路径里面
            count=count-cur->left->val; 
            traversal(cur->left,count);  //向左边递归,寻找左边是否有符合要求的路径
            count=count+cur->left->val;  //回溯:不管前面的向左递归有没有找到符合要求的路径,递归结束后都要回退到前面,以重新开始寻找新的路径
            path.pop_back();    //回溯:要回退到前面,就要把加进path的节点都弹出来
        }
        if(cur->right)
        {
            path.push_back(cur->right->val);
            count-=cur->right->val;
            traversal(cur->right,count);  //向右边递归,寻找右边是否有符合要求的路径
            count+=cur->right->val;
            path.pop_back();
        }

        return;  //这为什么要return
    }

public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        result.clear();
        path.clear();
        if(root==NULL) return result;
        path.push_back(root->val);  //因为后面递归直接就从左右孩子开始了,所以如果不先把根节点放进路径,就漏掉这个了
        traversal(root,targetSum-root->val);
        return result;

    }
};

18.从中序和后序遍历序列构造二叉树 106

思路分几步:

  • 第一步:如果数组大小为零的话,说明是空节点了。

  • 第二步:如果不为空,那么取后序数组最后一个元素作为中间节点元素。

  • 第三步:找到后序数组的中间节点元素在中序数组的位置,作为切割点。

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

  • 第五步:用切割出来的中序左数组切割后序数组,切成后序左数组和后序右数组

  • 第六步:递归处理左区间和右区间,即分成中序左数组、后序左数组去处理左边,中序右数组、后序右数组去处理右边

切割的时候左闭右开是怎么实现的vector默认左闭右开。按照这个原则去设置数组范围即可。

class Solution {
public:
    //先进行递归
    //参数及返回值:传入中序后序数组,要构建起一个二叉树,所以返回二叉树根节点
    TreeNode* traversal(vector<int>& inorder, vector<int>& postorder)
    {
        //终止条件:数组为空的话,返回空节点
        if(postorder.size()==0) return NULL;  //虽然主函数里也会有终止条件,但这个是递归的终止条件,两个不一样,不能省略

        //取后序数组最后一个元素作为根节点
        int rootValue=postorder[postorder.size()-1];
        TreeNode* root=new TreeNode(rootValue);

        //如果只有一个节点,那这个就是所构成的二叉树的根节点
        if(postorder.size()==1) return root;

        //在中序数组里寻找这个根节点,准备进行切割
        int index;
        for(index=0; index<inorder.size(); index++)
        {
            if(inorder[index]==rootValue) break;
        }

        //切割中序数组,得到中序左数组和中序右数组
        //注意是左闭右开,但我没懂这是咋体现出来的
        //中序左数组:[0,inorder.begin()+index)
        vector<int> leftInorder(inorder.begin(),inorder.begin()+index);
        //中序右数组:[inorder.begin()+index+1,inorder.end())  因为要把中间的根节点空开,所以是+1
        vector<int> rightInorder(inorder.begin()+index+1,inorder.end());

        //切割后序数组之前要先改一下数组大小,因为最后一个元素已经用过了
        postorder.resize(postorder.size()-1);

        //切割后序数组,得到后序左数组和后序右数组
        //后序左数组:[0,postorder.begin()+index)
        vector<int> leftPostorder(postorder.begin(),postorder.begin()+index);
        //后序右数组:[postorder.begin()+index,postorder.end())
        vector<int> rightPostorder(postorder.begin()+index,postorder.end());

        //向左向右递归,继续切割:传入中序后序数组,返回对应根节点
        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);
    }
};

下标索引法:这个方法是直接利用下标在原数组上操作,可以不用每次都构建新数组,极大地节省了时间和空间。

class Solution {
private:
    TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd)
    {
        if(postorderEnd==postorderBegin) return NULL;

        //构造中间根节点
        int rootValue=postorder[postorderEnd-1];   //因为传入的postoederEnd是后序数组的size,所以要访问最后一个元素的话,就要-1
        TreeNode* root=new TreeNode(rootValue);

        if(postorderEnd-postorderBegin==1) return root;

        //在中序数组中寻找根节点
        int index=0;
        for(index=inorderBegin; index<inorderEnd; index++)
        {
            if(inorder[index]==rootValue) break;
        }

        //切割中序数组
        //中序左数组
        int leftInorderBegin=inorderBegin;
        int leftInorderEnd=index;
        //中序右数组
        int rightInorderBegin=index+1;
        int rightInorderEnd=inorderEnd;  //左闭右开的区间,所以不包含inorderEnd,没有超

        //切割后序数组
        //后序左数组
        int leftPostorderBegin=postorderBegin;
        int leftPostorderEnd=postorderBegin+(index-inorderBegin);  //后序左数组结尾=开头+中序左数组的size
        //后序右数组
        int rightPostorderBegin=postorderBegin+(index-inorderBegin);
        int rightPostorderEnd=postorderEnd-1;

        root->left=traversal(inorder,leftInorderBegin,leftInorderEnd,postorder,leftPostorderBegin,leftPostorderEnd);
        root->right=traversal(inorder,rightInorderBegin,rightInorderEnd,postorder,rightPostorderBegin,rightPostorderEnd);

        return root;
    }


public:
    
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.size()==0 || postorder.size()==0) return NULL;
        return traversal(inorder,0,inorder.size(),postorder,0,postorder.size());   //左闭右开区间,不包含第size个元素
    }
};

如果使用构建新数组的方法,那么循环的起始和终止可以是0和数组的size();但如果使用下标索引的话,循环的起始和终止必须使用数组的起始和终止下标。

105.用前序和中序构造二叉树

class Solution {
private:
    TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd)
    {
        //终止条件
        if(preorderEnd-preorderBegin==0) return NULL;

        //构造中间根节点:前序数组的第一个值
        int rootValue=preorder[preorderBegin];
        TreeNode* root=new TreeNode(rootValue);

        //如果前序数组只有一个节点,那这个就是根节点
        if(preorderEnd-preorderBegin==1) return root;

        //在中序数组中寻找根节点,准备切割
        int index;
        for(index=inorderBegin; index<inorderEnd; index++)
        {
            if(inorder[index]==rootValue) break;
        }

        //切割中序数组
        //中序左数组
        int leftInorderBegin=inorderBegin;
        int leftInorderEnd=index;
        //中序右数组
        int rightInorderBegin=index+1;
        int rightInorderEnd=inorderEnd;

        //切割前序数组
        //前序左数组
        int leftPreorderBegin=preorderBegin+1;  //跳过第一个元素
        int leftPreorderEnd=preorderBegin+1+(index-inorderBegin);   //加上的是中序左数组的那个size
        //前序右数组
        int rightPreorderBegin=preorderBegin+1+(index-inorderBegin);
        int rightPreorderEnd=preorderEnd;

        root->left=traversal(preorder,leftPreorderBegin,leftPreorderEnd,inorder,leftInorderBegin,leftInorderEnd);
        root->right=traversal(preorder,rightPreorderBegin,rightPreorderEnd,inorder,rightInorderBegin,rightInorderEnd);

        return root;
    }

public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0 || inorder.size()==0) return NULL;
        return traversal(preorder,0,preorder.size(),inorder,0,inorder.size());
    }
};

19.最大二叉树 654

构造二叉树一定是:前序遍历

优化版本:使用下标索引,并且允许空数组进入递归,则不用在递归前进行判断

private:
    //递归构造二叉树
    //左闭右开区间[left,right)
    TreeNode* traversal(vector<int>& nums, int left, int right)
    {
        //终止条件:  
        if(left>=right) return NULL;

        //单层递归逻辑:中间节点
        //寻找数组中的最大值,作为根节点;同时用其下标作为分割点
        int maxValue=0;
        int index=left;
        for(int i=left; i<right; i++)
        {
            if(nums[i]>maxValue)
            {
                maxValue=nums[i];
                index=i;
            }
        }
        //构造根节点
        TreeNode* root=new TreeNode(0);
        root->val=maxValue;

        //向左递归
        int leftBegin=left;
        int leftEnd=index;
        root->left=traversal(nums,leftBegin,leftEnd);  //递归得到左子树的根节点
        
        //向右递归
        int rightBegin=index+1;
        int rightEnd=right;
        root->right=traversal(nums,rightBegin,rightEnd);

        return root;
    }


public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return traversal(nums,0,nums.size());
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值