力扣二叉树专题(五)- 左叶子之和、找树左下角的值、路径总和、从中序与后序遍历序列构造二叉树、从前序与中序遍历序列构造二叉树、最大二叉树 C++实现 总结

一、404.左叶子之和

注意点:

  1. 是左叶子,不是左节点!!只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。
  2. 左叶子值为0的情况,空节点,左叶子值为0;当前遍历的节点是叶子节点,左叶子值为0。

递归法-前序遍历-找到左叶子

class Solution {
public:
    //递归法
    int sumOfLeftLeaves(TreeNode* root) {
        //递归结束条件 空节点;叶子节点
        if(root==nullptr) return 0;
        if(root->left==nullptr && root->right==nullptr) return 0;
        int leftvalue = sumOfLeftLeaves(root->left);//左
        //找到左叶子 记录值
        if(root->left && root->left->left==nullptr && root->left->right==nullptr) 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;
        int sum = 0;
        if(root==nullptr) return 0;//空树不如栈
        st.push(root);
        while(!st.empty())
        {
            TreeNode* cur = st.top();
            st.pop();
            if(cur->left && !cur->left->left && !cur->left->right) sum += cur->left->val;
            if(cur->left) st.push(cur->left);
            if(cur->right) st.push(cur->right);
        }
        return sum;
    }
};

二、513. 找树左下角的值

class Solution {
public:
    int depth = 0;
    int leftvalue = 0;
    //版本1
    /*void maxdepth(TreeNode* cur, int level)
    {
        if(cur==nullptr) return;
        if(!cur->left && !cur->right && level==1) leftvalue = cur->val;//只有根节点
        if(level>depth && cur->left)//有左叶子
        {
            leftvalue = cur->left->val;
            depth = level;
        }
        if(level>depth && !cur->left)//没有左叶子
        {
            leftvalue = cur->val;
            depth = level;
        }
        if(cur->left) maxdepth(cur->left, level+1);
        if(cur->right) maxdepth(cur->right, level+1);
    }*/

	//优化 找到最底层最左节点放在递归结束里边
    void traversal(TreeNode* cur, int level)
    {
        if(cur->left==nullptr && cur->right==nullptr)
        {
            if(level>depth)
            {
                depth = level;
                leftvalue = cur->val;
            }
            return;
        }
        if(cur->left) traversal(cur->left, level+1);
        if(cur->right) traversal(cur->right, level+1);
        return;
    }
    
    int findBottomLeftValue(TreeNode* root) {
        //版本1
        //if(root==nullptr) return 0;
        //maxdepth(root, 1);

        //优化
        traversal(root, 1);
        return leftvalue;
    }
};

三、112. 路径总和

递归法 回溯
注意递归的返回值和参数,回溯的操作

  1. 递归参数,不用加法,累加来判断和与目标值是否相等比较麻烦,用减法比较简单
  2. 递归返回值,这里递归需要返回值,返回布尔类型
  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值——113.路径总和ii
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值——236. 二叉树的最近公共祖先
  • 如果要搜索其中一条符合条件的路径,递归一定需要返回值,因为遇到符合条件的路径了就要及时返回——本题
class Solution {
public:
    //递归 
    bool traversal(TreeNode* cur, int count)
    {
        //递归结束
        if(cur->left==nullptr && cur->right==nullptr && count==0) return true;//找到叶子节点,正好找到路径
        if(cur->left==nullptr && cur->right==nullptr) return false;//找到叶子节点,没有找到符合条件的路径
        //单次操作
        if(cur->left)
        {
            count -= cur->left->val;//减法
            if(traversal(cur->left, count)) return true;
            count += cur->left->val;//回溯
        }
        if(cur->right)
        {
            count -= cur->right->val;
            if(traversal(cur->right, count)) return true;
            count += cur->right->val;
        }
        return false;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root) return false;
        return traversal(root, targetSum - root->val);
    }
};

递归-单次操作优化

class Solution {
public:
    //递归 
    bool traversal(TreeNode* cur, int count)
    {
        //递归结束
        if(cur->left==nullptr && cur->right==nullptr && count==0) return true;//找到叶子节点,正好找到路径
        if(cur->left==nullptr && cur->right==nullptr) return false;//找到叶子节点,没有找到符合条件的路径
        //单次操作
        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;
        return false;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(!root) return false;
        return traversal(root, targetSum - root->val);
    }
};

四、106.从中序与后序遍历序列构造二叉树

  1. 以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
  2. 切割

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

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

第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

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

第五步:切割后序数组,切成后序左数组和后序右数组

第六步:递归处理左区间和右区间

切割过程中,保证左闭右开

class Solution {
public:
    TreeNode* traversal(vector<int>& inorder, vector<int>& postorder)
    {
        if(postorder.size()==0) return nullptr;

        //1.根节点就是后序数组的最后一个元素
        int rootvalue = postorder[postorder.size() - 1];
        //2.创建二叉树
        TreeNode* root = new TreeNode(rootvalue);
        // 叶子节点 递归结束
        if(postorder.size() == 1) return root;

        //找到中序遍历的切割点,找到根节点值的位置 分成左右两个中序左数组和中序右数组
        int delimiterIndex;
        for(delimiterIndex = 0; delimiterIndex<inorder.size(); delimiterIndex++)
        {
            if(inorder[delimiterIndex] == rootvalue) break;
        }

        //3.切割中序数组 左闭右开 [0, delimiterIndex)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        //3.切割中序数组 左闭右开 [delimiterIndex+1, end)
        vector<int> rightIndoer(inorder.begin() + delimiterIndex + 1, inorder.end());

        //4.切割后序数组 舍弃末尾元素(根节点)
        postorder.resize(postorder.size() - 1);

        //5.使用左中序数组大小作为切割点 后序左数组和后序右数组大小和中序左数组和中序右数组一样
        //左闭右开 [0, leftInorder.size)
        vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
        //左闭右开 [leftInorder.size, end)
        vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

        //6.递归
        root->left = traversal(leftInorder, leftPostorder);
        root->right = traversal(rightIndoer, 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.从前序与中序遍历序列构造二叉树

思路和106的一样,不同的是前序数组resize的地方

class Solution {
public:
    TreeNode* traversal(vector<int>& preorder, vector<int>& inorder)
    {
                if(preorder.size()==0) return nullptr;

        //1.根节点就是前序数组的第一个元素
        int rootvalue = preorder[0];
        //2.创建二叉树
        TreeNode* root = new TreeNode(rootvalue);
        // 叶子节点 递归结束
        if(preorder.size() == 1) return root;

        //找到中序遍历的切割点,找到根节点值的位置 分成左右两个中序左数组和中序右数组
        int delimiterIndex;
        for(delimiterIndex = 0; delimiterIndex<inorder.size(); delimiterIndex++)
        {
            if(inorder[delimiterIndex] == rootvalue) break;
        }

        //3.切割中序数组 左闭右开 [0, delimiterIndex)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        //3.切割中序数组 左闭右开 [delimiterIndex+1, end)
        vector<int> rightIndoer(inorder.begin() + delimiterIndex + 1, inorder.end());

        //4.切割前序数组 舍弃头部元素(根节点)
        preorder.assign(preorder.begin()+1, preorder.end());

        //5.使用左中序数组大小作为切割点 前序左数组和前序右数组大小和中序左数组和中序右数组一样
        //左闭右开 [0, leftInorder.size)
        vector<int> leftPreorder(preorder.begin(), preorder.begin() + leftInorder.size());
        //左闭右开 [leftInorder.size, end)
        vector<int> rightPreorder(preorder.begin() + leftInorder.size(), preorder.end());

        //6.递归
        root->left = traversal(leftPreorder, leftInorder);
        root->right = traversal(rightPreorder, rightIndoer);

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

六、654.最大二叉树

构造树-前序遍历-递归:最大值构造中间节点,最大值左区间构造左子树,最大值右区间构造右子树
不允许空节点进入递归,遇到叶子节点就终止递归

class Solution {
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        TreeNode* node = new TreeNode(0);//创建新节点,数组的值赋给节点,构造树
        //1.递归条件 数组只有一个数说明遍历到叶子节点了
        if(nums.size()==1)
        {
            node->val = nums[0];//数组的值赋给节点
            return node;
        }
        //2.找最大值作为根节点 划分左右区间
        int maxValue = 0;
        int maxIndex = 0;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]>maxValue)
            {
                maxValue = nums[i];
                maxIndex = i;
            }
        }
        node->val = maxValue;//中
        //3.最大值左区间 作为左子树 递归 要保证左区间至少有一个数
        if(maxIndex > 0)
        {
            vector<int> tree(nums.begin(), nums.begin()+maxIndex);
            node->left = constructMaximumBinaryTree(tree);//左
        }
        //4.最大值右区间 作为右子树 递归 要保证右区间至少有一个数
        if(maxIndex < (nums.size()-1))
        {
            vector<int> tree(nums.begin()+ maxIndex + 1, nums.end());
            node->right = constructMaximumBinaryTree(tree);//右
        }
        return node;
    }
};

优化,思路一样,还是前序遍历-递归:最大值构造中间节点,最大值左区间构造左子树,最大值右区间构造右子树
但是允许空节点进入递归,遇到空节点,也就是数组区间为0,就终止递归

class Solution {
public:
    TreeNode* traversal(vector<int>& nums, int left, int right)
    {
        if(left >= right) return nullptr;//数组区间为0,遇到空节点,递归结束
        //划分区间 找到最大值的位置
        int maxIndex = left;
        for(int i=left; i<right; i++)
        {
            if(nums[i]>nums[maxIndex]) maxIndex = i;

        }
        TreeNode* node = new TreeNode(nums[maxIndex]);//中
        node->left = traversal(nums, left, maxIndex);//左 保证区间左闭右开 [left, maxValueIndex)
        node->right = traversal(nums, maxIndex+1, right);//右 保证区间左闭右开 [maxValueIndex + 1, right)
        return node;
    }
    TreeNode* constructMaximumBinaryTree(vector<int>& nums)
    {
        return traversal(nums, 0, nums.size());
    }
};

总结

题一:通过节点的父节点以及孩子节点来判断本节点的属性

题二:找树的最后一行最左边的值,深度最大的叶子节点一定是最后一行

题三:一般情况下,如果需要搜索整棵二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回

题四:构造二叉树注意点

  • 分割时候,坚持区间不变量原则,左闭右开,或者左闭又闭
  • 分割的时候,注意后序/前序已经有一个节点作为中间节点了,不能继续使用了
  • 如何使用切割后的后序/前序数组来切合中序数组?利用中序数组大小一定是和后序/前序数组的大小相同这一特点来进行切割
  • 前序和后序不能唯一构成一棵二叉树,因为没有中序遍历就无法确定左右部分,也就无法分割

题五:

  • 注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
  • 一般情况来说,如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值