day18 | 513.找树左下角的值、路径总和、 从中序与后序遍历序列构造二叉树

目录:

链接

题目链接:

https://leetcode.cn/problems/find-bottom-left-tree-value/

解题及思路学习

513. 找树左下角的值

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

!https://assets.leetcode.com/uploads/2020/12/14/tree1.jpg

输入:root = [2,1,3]
输出:1

思考:递归和迭代都可以。

递归:题目关键点,最底层,所以,还需要知道子树的深度。可以考虑返回一个数组信息,第一个传的是该分支最左边节点的值,第二个传深度信息,也就是该分支的高度。

根据随想录写的代码:

class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void reversal(TreeNode* root, int depth) {
        if (root->left == NULL && root ->right == NULL) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root->val;
            }
        }
        if (root->left) {
            depth++;
            reversal(root->left, depth);
            depth--;// 回溯
        }
        if (root->right) {
            depth++;
            reversal(root->right,depth);
            depth--; // 回溯
        }
        return;
    }

    int findBottomLeftValue(TreeNode* root) {
        reversal(root, 0);
        return result;
        
    }
};

迭代法:

一直用一个值保存每行的第一个,遍历结束后就放的是最底层的第一个。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que;
        int result;
        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;
    }
};

递归的处理方法很妙,我原先想的是返回一个数组,分别记录高度和最左节点的值。随想录里面是在递归外层用变量记录最大的深度和对于的值。这种方法比我刚开始思考的方法传回的值更简洁。后面如果遇到这种需要记录的题目,也可以考虑用这种回溯的思路去做,在递归的外层用变量记录。

路径总和

112. 路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

叶子节点 是指没有子节点的节点。

示例 1:

!https://assets.leetcode.com/uploads/2021/01/18/pathsum1.jpg

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

思考:这题可以用前序递归来试一下,用一个数组记录一下从根节点到当前叶子节点的路径和。

代码:

这里就很明显用到了回溯的思想,比较坑的是结束情况需要考虑的更清楚。起先没想到root为空,判断条件少了一个,卡了我将近二十分钟。

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

        if (root->left) {
            targetSum -= root->val;
            if(hasPathSum(root->left,targetSum)) return true;
            targetSum += root->val;
        }

        if (root->right) {
            targetSum -= root->val;
            if(hasPathSum(root->right, targetSum)) return true;
            targetSum += root->val;
        }
        return false;
    }
};

迭代也能行,试着写一下。没写出来,直接看随想录代码:

递归代码:

思路差不多一样的

class Solution {
private:
    bool traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (!cur->left && !cur->right) 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;
    }

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

//精简版代码
lass Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (!root) return false;
        if (!root->left && !root->right && sum == root->val) {
            return true;
        }
        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
    }
};

迭代法

利用一个stack栈,用pair的形式存储当前节点的指针以及路径的累计数值。很秒。

class solution {

public:
    bool haspathsum(TreeNode* root, int sum) {
        if (root == null) return false;
        // 此时栈里要放的是pair<节点指针,路径数值>
        stack<pair<TreeNode*, int>> st;
        st.push(pair<TreeNode*, int>(root, root->val));
        while (!st.empty()) {
            pair<TreeNode*, int> node = st.top();
            st.pop();
            // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
            if (!node.first->left && !node.first->right && sum == node.second) 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. 路径总和 II

这道题升级了一下难度,返回的不再是bool,而是所有等于目标和的路径。

递归方法:

自己写的方法,时间复杂度有点高,如果将vec换成指针,或许会快不少。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> vec;
    void findPath(TreeNode* root, int targetSum, vector<int> vec) {
        if (root == NULL) return;
        if (!root->left && !root->right && targetSum == root->val) {
            vec.push_back(root->val);
            result.push_back(vec);
        }
        if (!root->left && !root->right && targetSum != root->val) return;

        if (root->left) {
            vec.push_back(root->val);
            targetSum -= root->val;
            findPath(root->left, targetSum, vec);
            vec.pop_back();
            targetSum += root->val;
        }
        if (root->right) {
            vec.push_back(root->val);
            targetSum -= root->val;
            findPath(root->right, targetSum, vec);
            vec.pop_back();
            targetSum += root->val;
        }
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        findPath(root, targetSum, vec);
        return result;
    }
};

// 优化版本, 可以不用传入vec
class Solution {
public:
    vector<vector<int>> result;
    vector<int> vec;
    void findPath(TreeNode* root, int targetSum) {
        if (root == NULL) return;
        if (!root->left && !root->right && targetSum == root->val) {
            vec.push_back(root->val);
            result.push_back(vec);
            vec.pop_back();
        }
        if (!root->left && !root->right && targetSum != root->val) return;

        if (root->left) {
            vec.push_back(root->val);
            targetSum -= root->val;
            findPath(root->left, targetSum);
            targetSum += root->val;
            vec.pop_back();
        }
        if (root->right) {
            vec.push_back(root->val);
            targetSum -= root->val;
            findPath(root->right, targetSum);
            targetSum += root->val;
            vec.pop_back();
        }
        return;
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        result.clear();
        vec.clear();
        findPath(root, targetSum);
        return result;
    }
};

随想录方法:

递归

class solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    // 递归函数不需要返回值,因为我们要遍历整个树
    void traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
            result.push_back(path);
            return;
        }

        if (!cur->left && !cur->right) return ; // 遇到叶子节点而没有找到合适的边,直接返回

        if (cur->left) { // 左 (空节点不遍历)
            path.push_back(cur->left->val);
            count -= cur->left->val;
            traversal(cur->left, count);    // 递归
            count += cur->left->val;        // 回溯
            path.pop_back();                // 回溯
        }
        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 ;
    }

public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        result.clear();
        path.clear();
        if (root == NULL) return result;
        path.push_back(root->val); // 把根节点放进路径
        traversal(root, sum - root->val);
        return result;
    }
};

递归过程中当需要记录一些值,这些值在遍历的过程中发生变化,就一定要注意回溯的写法。

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

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例 1:

!https://assets.leetcode.com/uploads/2021/02/19/tree.jpg

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

思考:简单的说,就是给定中序遍历和后续遍历,去构造这颗二叉树。自己想的步骤→ 先从后续遍历取到末尾的值,再到中序遍历中找到该值,根据该值将其分为左右两个部分。然后根据中序的两个区间再将后续分成两个区间。然后重复该步骤。

随想录:思路差不多。以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

来看一下一共分几步:

  • 第一步:如果数组大小为零的话,说明是空节点了。
  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
  • 第五步:切割后序数组,切成后序左数组和后序右数组
  • 第六步:递归处理左区间和右区间
class Solution {
private:
    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 delimiterIndex;
        for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }

        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
        postorder.resize(postorder.size()-1);

        vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
        vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());

        root->left = traversal(leftInorder, leftPostorder);
        root->right = traversal(rightInorder, rightPostorder);
        return root;
    }

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

    }
};

看起来真的很复杂,做题的时候需要仔细判断边界条件,最好都用某一种边界条件。

困难及收获

困难

思路清晰, 代码写出来一定是各种问题

今日收获

对于调代码,阔以用日志的方法来搞清楚细节

https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html#思路

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值