代码随想录算法训练营第18天| Leetcode 513.找树左下角的值、112. 路径总和、113. 路径总和ii、106.从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树

目录

Leetcode 513.找树左下角的值

Leetcode 112. 路径总和

Leetcode 113. 路径总和ii

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

Leetcode 105.从前序与中序遍历序列构造二叉树


Leetcode 513.找树左下角的值

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

题目描述:给定一个二叉树,在树的最后一行找到最左边的值。

思路:首先要注意题目寻找的是最后一行的最左边节点的值,这个节点不一定是左孩子,也可能是右孩子。这道题既可以用递归法也可以用迭代法。递归寻找最大深度或者一层一层迭代寻找最大深度。当然这两种方法都是基于二叉树的遍历代码实现的,不了解的可以先看看这两个:深度优先遍历        层序遍历

代码如下:(递归法)

class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void traverssal(TreeNode* root, int depth) {
        if (root->left == nullptr &&
            root->right == nullptr) { //找到叶子节点就判断是否需要更新
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root->val;
            }
            return;
        }
        if (root->left) { //先向左找
            traverssal(root->left, depth + 1);
        }
        if (root->right) { //左边没有就去右面找
            traverssal(root->right, depth + 1);
        }
        return;
    }
    int findBottomLeftValue(TreeNode* root) {
        traverssal(root, 0);
        return result;
    }
};

(迭代法)

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

Leetcode 112. 路径总和

题目链接:Leetcode 112. 路径总和

题目描述:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

思路:通过遍历二叉树,判断路径之和是否符合题意。这里有一个小技巧,每访问过一个节点就用目标值减去该节点的值,最后结果为0的就是目标路径。

代码如下:(递归法)

class Solution {
public:
    bool traversal(TreeNode* cur, int count) {
        //找到目标路径直接返回true
        if (cur->left == nullptr && cur->right == nullptr && count == 0)
            return true;
        //找到叶子节点但不是目标路径,返回false
        if (cur->left == nullptr && cur->right == nullptr)
            return false;
        //如果左子树找到了,则向上返回true
        if (cur->left && traversal(cur->left, count - cur->left->val))
            return true;
        //如果右子树找到了,则向上返回true
        if (cur->right && traversal(cur->right, count - cur->right->val))
            return true;
        //说明左右子树都没找到,返回false
        return false;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr)
            return false; //空树一定不存在
        return traversal(root, targetSum - root->val);
    }
};

(迭代法)

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()) {
            pair<TreeNode*, int> node = st.top();
            st.pop();
            // 如果该节点是叶子节点了,同时该节点的路径数值等于targetSum,那么就返回true
            if (!node.first->left && !node.first->right &&
                targetSum == 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;
    }
};

Leetcode 113. 路径总和ii

题目链接:Leetcode 113. 路径总和ii

题目描述:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。

说明: 叶子节点是指没有子节点的节点。

思路:这道题和上道题类似,上道题只需要判断是否有目标路径,这道题需要将所有合适的路径返回。本题用栈模拟代码书写比较繁琐,但是逻辑和上道题的类似:都是用栈模拟递归遍历二叉树。

代码如下:(递归法)

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right &&
            count == 0) { //找到叶子节点并且路径之和等于targetSum
            result.push_back(path);
            return;
        }
        if (!cur->left && !cur->right)
            return;
        if (cur->left) {
            path.push_back(cur->left->val);
            traversal(cur->left, count - cur->left->val); //向下访问
            path.pop_back(); //回溯(访问后将path恢复原状)
        }
        if (cur->right) {
            path.push_back(cur->right->val);
            traversal(cur->right, count - cur->right->val); //向下访问
            path.pop_back(); //回溯(访问后将path恢复原状)
        }
        return;
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        //result.clear();
        //path.clear();
        if (root == nullptr)
            return result;
        path.push_back(root->val);
        traversal(root, targetSum - root->val);
        return result;
    }
};

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

题目链接:Leetcode 106.从中序与后序遍历序列构造二叉树

题目描述:根据一棵树的中序遍历与后序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

在本题开始之前,大家可以试着询问自己这样一件事:给定一颗二叉树,能否熟练的写出该二叉树的前、中、后序遍历的节点顺序。举个例子,下面这颗二叉树的前、中、后序遍历能否熟练说出来呢?

前序遍历:6427153

中序遍历:2741653

后序遍历:7214356

如果能够熟练的写出这三种序列,理解这道题与下道题的思路会更加容易。

思路:我们发现前序遍历的第一个数后序遍历的最后一个数当前二叉树的根节点。再观察中序遍历,我们发现根节点6左侧的数字是左子树的节点,6右侧的数字是右子树的节点。这种性质的原理来源于前、中、后序的遍历顺序。基于上述思路,我们可以尝试将思路转化为代码的逻辑:

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

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

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

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)切完之后别忘了删掉后序数组的最后一个数字。

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

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

尽管有了上述思路,敲上代码之后发现切割的边界问题也是一个易错点:一定要保证切割范围的一致性。说人话就是切割的区间要么是左闭右闭区间,要么是左闭右开区间(一般只有这两种写法)

代码如下:(每次递归通过新建数组体现出分割)

class Solution {
public:
    TreeNode* traversal(vector<int>& inorder, vector<int>& postorder) {
        if (postorder.size() == 0)
            return nullptr;
        //后序遍历数组最后一个元素就是当前的中间节点
        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;
        }
        //切割中序数组
        vector<int> leftInorder(inorder.begin(), inorder.begin() + index);
        vector<int> rightInorder(inorder.begin() + index + 1, inorder.end());
        // postorder舍弃末尾元素
        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;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0)
            return nullptr;
        return traversal(inorder, postorder);
    }
};

当然这种写法只是方便理解“切割数组”这一思想,我们也可以通过切割数组下标来实现:

代码如下:(每次递归利用下标分割)

class Solution {
public:
    TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd,
                        vector<int>& postorder, int postorderBegin,
                        int postorderEnd) {
        if (postorderBegin == postorderEnd)
            return nullptr;
        //保存后序数组最后一个节点
        int rootValue = postorder[postorderEnd - 1];
        TreeNode* root = new TreeNode(rootValue);
        //说明后序遍历数组为空
        if (postorderEnd - postorderBegin == 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 leftPostorderBegin = postorderBegin;
        // 终止位置是 需要加上 中序区间的大小size
        int leftPostorderEnd = postorderBegin + index - inorderBegin;
        //右区间
        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;
    }
    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());
    }
};

Leetcode 105.从前序与中序遍历序列构造二叉树

题目链接:Leetcode 105.从前序与中序遍历序列构造二叉树

题目描述:根据一棵树的前序遍历与中序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

思路:和上面那道题类似,只是切割数据从最后一个节点数据变成了第一个节点数据。

代码如下:(每次递归利用下标分割)

class Solution {
public:
    TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd,
                        vector<int>& preorder, int preorderBegin,
                        int preorderEnd) {
        if (preorderBegin == preorderEnd)
            return nullptr;

        int rootValue = preorder[preorderBegin]; // 注意用preorderBegin 不要用0
        TreeNode* root = new TreeNode(rootValue);

        if (preorderEnd - preorderBegin == 1)
            return root;

        int index;
        for (index = inorderBegin; index < inorderEnd; index++) {
            if (inorder[index] == rootValue)
                break;
        }
        // 切割中序数组
        // 中序左区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = index;
        // 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = index + 1;
        int rightInorderEnd = inorderEnd;

        // 切割前序数组
        // 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
        int leftPreorderBegin = preorderBegin + 1;
        int leftPreorderEnd =
            preorderBegin + 1 + index -
            inorderBegin; // 终止位置是起始位置加上中序左区间的大小size
        // 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
        int rightPreorderBegin = preorderBegin + 1 + (index - inorderBegin);
        int rightPreorderEnd = preorderEnd;

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

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

        // 参数坚持左闭右开的原则
        return traversal(inorder, 0, inorder.size(), preorder, 0,
                         preorder.size());
    }
};

总结:最近的题目越来越综合性,但凡二叉树的基础知识,递归、迭代思想的代码实现,以及二叉树的基本遍历方式等内容不太熟悉,理解题解思路都会吃力,不过慢慢熟悉这些内容之后再看这些题目,有种“轻舟已过万重山”的感觉!

最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!

  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有数组中的元素进行平方,并按照非递减的顺返回结果。这里由于数组已经是有的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值