16.树(5) | 找树左下角的值(h)、路径总和、路径总和 II、从中序与后序遍历序列构造二叉树、从前序与中序遍历序列构造二叉树

        今天的几道题重点在于递归解法,需要再次熟悉树的遍历和回溯方法,并了解递归建树的方法,以及递归返回值的不同类型。

        二叉树递归返回值总结(来自代码随想录):

  • 需要返回值的情况:
  1. 要搜索其中一条符合条件的路径即停止的情况;
  2. 要搜索整棵二叉树,且需要处理递归返回值。
  • 无需返回值的情况:要搜索整棵二叉树,但不用处理递归返回值。

        第1道题(513.找树左下角的值)的递归解法自己的思路是比较左、右高度,左子树高度 ≥ 右子树高度时递归查找左子树,否则查找右子树。其中计算树高度用后序遍历的递归方法。

class Solution {
public:
    int getDepth(TreeNode *cur) {
        if (cur == nullptr) {
            return 0;
        }
        int depthLeft = getDepth(cur->left);
        int depthRight = getDepth(cur->right);
        return 1 + max(depthLeft, depthRight);
    }
    int findBottomLeftValue(TreeNode* root) {
        if (root->left == nullptr && root->right == nullptr) {
            return root->val;
        }
        if (getDepth(root->left) >= getDepth(root->right)) {
            return findBottomLeftValue(root->left);
        }
        return findBottomLeftValue(root->right);
    }
};

        题解的递归解法则用了前序遍历(中、后序也可以,因为都是左节点优先,符合题目要求),在遍历到每个节点时都取当前深度,然后每遇到深度更大的节点来更新结果为当前节点值。这道题的返回值是不需要返回值对应的情形。

class Solution {
public:
    int ans, maxDepth;
    void getDepth(TreeNode *cur, int depth) {
        if (cur->left == nullptr && cur->right == nullptr) {
            if (depth > maxDepth) {
                maxDepth = depth;
                ans = cur->val;
            }
            return;
        }
        if (cur->left) {
            getDepth(cur->left, depth + 1);
        }
        if (cur->right) {
            getDepth(cur->right, depth + 1);
        }
        return;
    }
    int findBottomLeftValue(TreeNode* root) {
        maxDepth = 0;
        getDepth(root, 1);
        return ans;
    }
};

值得一提的是,代码第13行和第16行使用了回溯,只不过将depth + 1作为参数,是隐式的回溯,而这也是前序遍历求深度的标准方法。实现过程中,在getDepth()中分别递归左、右节点时忘记了首先判断左、右节点是否存在,导致了空指针错误。

        迭代法比较简单,只需要用层序遍历记录最后一层的第一个元素。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        int ans;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();
            ans = que.front()->val;
            while (size--) {
                TreeNode *cur = que.front();
                que.pop();
                if (cur->left) {
                    que.push(cur->left);
                }
                if (cur->right) {
                    que.push(cur->right);
                }
            }
        }
        return ans;
    }
};

        二刷:忘记题解解法。


        第2题(112. 路径总和)自己的思路是将叶子节点作为递归出口返回true或false,而对于非叶子节点,则返回左子树的结果与右子树的结果相或,作为最终结果。

class Solution {
public:
    bool traversal (TreeNode *cur, int sumNow, int targetSum) {
        sumNow += cur->val;
        if (cur->left == nullptr && cur->right == nullptr) {
            if (sumNow == targetSum) {
                return true;
            }
            return false;
        }
        bool resLeft = false, resRight = false;
        if (cur->left) {
            resLeft = traversal(cur->left, sumNow, targetSum);
        }
        if (cur->right) {
            resRight = traversal(cur->right, sumNow, targetSum);
        }
        return resLeft || resRight;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) {
            return false;
        }
        return traversal(root, 0, targetSum);
    }
};

自己的思路虽然AC,但因为多一个参数,所以需要额外的递归函数。题解则没有额外的参数,实现了原地递归。其实现基于对targetSum做减法而非加法,每次递归targetSum都减去当前节点的val,在遇到叶子节点时判断当前targetSum值是否为0即可获知是否有满足条件的路径。这道题的返回值是需要返回值中的第1种类型。

class Solution {   
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) {
            return false;
        }
        if (root->left == nullptr && root->right == nullptr) {
            if (root->val == targetSum) {
                return true;
            }
            return false;
        }
        return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
    }
};

其中第11行是不必要的,不过如果去掉的话会多进行一次递归。

        迭代解法则直接看了题解,直接套用前序递归模板即可,需要在递归过程中将当前的路径和值也记录下来,遇到叶子节点时判断当前路径和是否等于targetSum即可。

class Solution {   
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) {
            return false;
        }
        stack<pair<TreeNode*, int>> st;
        st.push(pair<TreeNode*, int>(root, root->val));
        while (!st.empty()) {
            TreeNode *cur = st.top().first;
            int sum = st.top().second;
            st.pop();
            if (cur->left == nullptr && cur->right == nullptr && sum == targetSum) {
                return true;
            }
            if (cur->right) {
                st.push(pair<TreeNode*, int>(cur->right, sum + cur->right->val));
            }
            if (cur->left) {
                st.push(pair<TreeNode*, int>(cur->left, sum + cur->left->val));
            }
        }
        return false;
    }
};

代码中需要熟悉pair的使用,要注意pair的“<>”中永远是数据类型,而在创建pair实例时也要用“<>”注明数据类型,再用“()”填充数据。在读取pair数据时,first与second不是函数,而是值,所以不加“()”。

        二刷:遇到叶子节点才能判断targetSum是否为0。


        第3题(113. 路径总和 II)是第2题(112. 路径总和)的多答案版本,返回值是无需返回值对应的情形。相比上一题需要改变为遍历整个二叉树,并在中途运用回溯记录路径,在叶子节点处将符合要求的路径都添加进结果。

class Solution {
public:
    void traversal(TreeNode *cur, int targetSum, vector<int> path, vector<vector<int>>& res) {
        if (cur->left == nullptr && cur->right == nullptr) {
            if (targetSum == 0) {
                res.push_back(path);
            }
            else {
                return;
            }
        }
        if (cur->left) {
            path.push_back(cur->left->val);
            traversal(cur->left, targetSum - cur->left->val, path, res);
            path.pop_back();
        }
        if (cur->right) {
            path.push_back(cur->right->val);
            traversal(cur->right, targetSum - cur->right->val, path, res);
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>> res;
        if (root == nullptr) {
            return res;
        }
        vector<int> path;
        path.push_back(root->val);
        traversal(root, targetSum - root->val, path, res);
        return res;
    }
};

题解中的递归参数只用了2个,而将path和res设置为了全局遍变量,并在主函数开始处对其clear()。

        迭代解法因为要在遍历过程中保留路径信息,所以在栈中除了要存储节点,sum外,还需存储一个用于保存当前节点路径的vector,实现较为麻烦,没有必要,所以没有实现。


        第4道题(106.从中序与后序遍历序列构造二叉树)需要递归建树节点,在每个递归中都建一个节点,最后也返回该节点。中间首先设立递归出口,自己的实现使用左闭右闭的区间表示方法,所以出口即为中序(或后序)遍历的左下标与右下标相等,此时说明该节点是叶子节点,对其赋值并return即可。否则,就需要分别找到中序遍历的中间节点,和后序遍历下一轮的中间节点:

  •  后序遍历最后一位数字即为这一轮的中间节点,在中序遍历里查找该值,以此找到中序遍历在这一轮的中间节点。
  • 找到中序遍历在这一轮的中间节点后,中序遍历就被中间节点划分为左、右两部分,两部分各自的长度与后序遍历中两部分的长度一定相等,且后序遍历中左边部分也一定位于右边部分的左边(前面)。利用这一点得到后序遍历的划分。

得到两个遍历的划分后,分别判断左右子树是否存在(划分后的长度 ≥ 1 则存在),如果存在就进行递归建树,将结果赋给当前节点的left/right。最后返回当前节点。方法的重点是对递归时的各个数组下标把握正确。

class Solution {
public:
    TreeNode* traversal(vector<int>& inorder, int indLeftIn, int indRightIn, vector<int>& postorder, int indLeftPost, int indRightPost) {
        TreeNode *cur = new TreeNode();
        if (indRightIn == indLeftIn) {
            cur->val = inorder[indLeftIn];
            return cur;
        }
        int indRoot = indLeftIn;
        while (inorder[indRoot] != postorder[indRightPost]) {
            indRoot++;
        }
        cur->val = inorder[indRoot];
        int lenLeft = indRoot - indLeftIn;
        int indLeftIn1 = indLeftIn;
        int indRightIn1 = indRoot - 1;
        int indLeftPost1 = indLeftPost;
        int indRightPost1 = indLeftPost + lenLeft - 1;
        if (indRoot > indLeftIn) {
            cur->left = traversal(inorder, indLeftIn1, indRightIn1, postorder, indLeftPost1, indRightPost1);
        }
        int indLeftIn2 = indRoot + 1;
        int indRightIn2 = indRightIn;
        int indLeftPost2 = indLeftPost + lenLeft;
        int indRightPost2 = indRightPost - 1;
        if (indRoot < indRightIn) {
            cur->right = traversal(inorder, indLeftIn2, indRightIn2, postorder, indLeftPost2, indRightPost2);
        }
        return cur;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) {
            return nullptr;
        }
        return traversal(inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1);
    }
};

实现过程中出现过以下几个错误:

  • 新建节点时只是声明了指针而没有new TreeNode();
  • 错误在创建左节点时用了lenLeft和lenRight,创建右节点时也用了lenLeft和lenRight。应该是创建左、右节点时仅用lenLeft,不需要计算和使用lenRight;
  • 在计算postorder的右半边部分左下标时,错误按照inorder的计算方法计算,写成了indLeftPost + lenLeft + 1,应该是indLeftPost + lenLeft,因为postorder的中间节点在最后一位,而不像inorder一样在中间;
  • 在计算postorder的右半边部分右下标时,错误按照inorder的计算方法计算,写成了indRightPost,应该是indRightPost - 1,因为postorder的中间节点在最后一位,而不像inorder一样在中间;

另外也可以将中序、后序数组作为参数传递,vector取某一部分的写法为:

vector<int> leftInorder(inorder.begin(), inorder.begin() + len)

 不过这样会导致递归过程中大量的vector复制过程,导致效率下降。

        二刷

        忘记find()用法,即find(vec.begin(), vec.end(), target),它返回一个指向该元素的迭代器,可以通过计算迭代器和vector开头之间的距离来获得该元素在vector中的位置。对应头文件是<algorithm>;

        在find()的使用上出错,应该是int lenLeft =  find(inorder.begin(), inorder.end(), val) - (inorder.begin() + inBegin),忘记加最后的inBegin;

        忘记在创建左、右子节点时加if()。


        第5道题(105.从前序与中序遍历序列构造二叉树)思路和解法与上一题一样,只是中间节点从后序遍历的末位变为了前序遍历的首位。

class Solution {
public:
    TreeNode* traversal(vector<int>& preorder, int indPreBegin, int indPreEnd, vector<int>& inorder, int indInBegin, int indInEnd) {
        TreeNode *cur = new TreeNode();
        if (indPreBegin == indPreEnd) {
            cur->val = preorder[indPreBegin];
            return cur;
        }
        int indMid = indInBegin;
        while (inorder[indMid] != preorder[indPreBegin]) {
            indMid++;
        }
        cur->val = inorder[indMid];
        int lenLeft = indMid - indInBegin;
        int indPreBeginLeft = indPreBegin + 1;
        int indPreEndLeft = indPreBegin + 1 + lenLeft - 1;
        int indInBeginLeft = indInBegin;
        int indInEndLeft = indMid - 1;
        if (indMid > indInBegin) {
            cur->left = traversal(preorder, indPreBeginLeft, indPreEndLeft, inorder, indInBeginLeft, indInEndLeft);
        }
        int indPreBeginRight = indPreBegin + 1 + lenLeft;
        int indPreEndRight = indPreEnd;
        int indInBeginRight = indMid + 1;
        int indInEndRight = indInEnd;
        if (indMid < indInEnd) {
            cur->right = traversal(preorder, indPreBeginRight, indPreEndRight, inorder, indInBeginRight, indInEndRight);
        }
        return cur;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() == 0 || inorder.size() == 0) {
            return nullptr;
        }
        return traversal(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1);
    }
};

实现过程中忘写了第13行的内容,导致中间节点没有赋值,默认赋值为0。

        最后,前序和后序不能唯一确定一棵二叉树,因为如果缺少中序,那么左、中、右三个节点组成的单元中的左、右子树将无法分辨,无法划分为左、右两部分。如

                                        1                                                            1

                                ↙                                                                        ↘

                        2                                          与                                           2

                ↙                                                                                                      ↘

        3                                                                                                                         3

         二刷:确定中间节点的val时,把int val = preorder[pre1]错误写成int val = preorder[0]。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值