代码随想录算法训练营day16

代码随想录算法训练营

—day16


前言

今天是算法营的第16天,希望自己能够坚持下来!
今日任务:
● 513.找树左下角的值
● 112. 路径总和
● 106.从中序与后序遍历序列构造二叉树
● 105.从前序与中序遍历序列构造二叉树


一、513.找树左下角的值

题目链接
文章讲解
视频讲解

递归法

在树的最后一行找到最左边的值。
找最后一行?找最大深度的那一层。用一个depth记录深度。
如何找最左边的呢?使用前序法,中左右,确保深度最大时,优先遍历左节点,保存左节点的值。

  1. 递归函数的参数和返回值:参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。
  2. 终止条件:遇到叶子节点了,统计最大深度,记录当前节点值。
  3. 单层递归的逻辑:在找最大深度的时候,递归的过程中要使用回溯。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int result;
    int maxDepth;

    void traversal (TreeNode* node, int depth) {
        //当遍历到叶子节点的时候终止
        if (node->left == nullptr && node->right == nullptr) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = node->val;
                return;
            }
        }
        //因为是要找最左边的节点,所以只要确保先遍历左边就可以了
        if (node->left) traversal(node->left, depth + 1); //这里将回溯隐藏在了参数里
        if (node->right) traversal(node->right, depth + 1);

        return;
    }

    int findBottomLeftValue(TreeNode* root) {
        result = 0;
        maxDepth = INT_MIN; //要求最大值,先定义成最小值
        if (root == nullptr) return result;

        traversal(root, 1);
        return result;
    }
};

层序遍历法

记录每层的最左边的节点值即可。
代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que;
        int result = 0;
        if (root == nullptr) return result;

        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. 路径总和

题目链接
文章讲解
视频讲解

递归法

用目标值减去节点值来判断,最后target=0 说明路径总和等于目标值
有一条路径满足条件就可以立即返回true
这到底用什么遍历方式都可以,因为只是计算节点之和,没有顺序要求

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //用目标值减去节点值来判断,路径总和是否等于目标值
    bool traversal(TreeNode* node, int target) {
        if (node->left == nullptr && node->right == nullptr && target == 0) return true;
        if (node->left == nullptr && node->right == nullptr) return false;

        //有一条路径满足条件就可以立即返回true
        if (node->left) {
            if (traversal(node->left, target - node->left->val)) return true;
        }

        if (node->right) {
            if (traversal(node->right, target - node->right->val)) return true;
        }

        return false;
    }

    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return false;
        //因为我们在递归里面没有处理根节点的值,所以先减了再放进去
        return traversal(root, targetSum - root->val);
    }
};

递归法精简版

每次减去当前节点的值,最后进入叶子节点时,目标值等于叶子节点值则为true
代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //精简版递归
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (!root) return false; //空节点判断移到开头
        if (!root->left && !root->right && targetSum == root->val) return true;
        //每次减去当前节点的值,最后进入叶子节点时,目标值等于叶子节点值则为true
        return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
    }
};

迭代法

用迭代法模拟递归,思路是一样的,但是需要同时保存节点和目标值。
在c++中可以用pair来保存两个参数。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        //用pair同时存放节点和目标值
        stack<pair<TreeNode*, int>> st;
        if (root == nullptr) return false;

        st.push(pair<TreeNode*, int> (root, targetSum));
        while (!st.empty()) {
            TreeNode* node = st.top().first;
            int target = st.top().second;
            st.pop();
            // 如果该节点是叶子节点了,同时该节点的路径数值等于target,那么就返回true
            if (!node->left && !node->right && target == node->val) return true;

            if (node->left) st.push(pair<TreeNode*, int> (node->left, target - node->val)); //左
            if (node->right) st.push(pair<TreeNode*, int> (node->right, target - node->val)); //右
        }

        return false;
    }
};

相关题目:113. 路径总和 II

题目链接

思路是一样的,只是变成要记录路径。使用前序遍历,多用一个数组来存储遍历过的节点,并且注意回溯,还有根节点的处理。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> result;

    //用目标值减去节点值来判断,路径总和是否等于目标值,path记录本次遍历的路径,满足条件就添加到结果集
    void traversal(TreeNode* node, vector<int> path, int targe) {
        if (node->left == nullptr && node->right == nullptr && targe == 0) {
            result.push_back(path);
            return;
        }
        if (node->left == nullptr && node->right == nullptr) {
            return;
        }

        if (node->left) {
            path.push_back(node->left->val);
            traversal(node->left, path, targe - node->left->val);
            path.pop_back(); //回溯
        }

        if (node->right) {
            path.push_back(node->right->val);
            traversal(node->right, path, targe - node->right->val);
            path.pop_back(); //回溯
        }
        return;
    }

    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return result;
        vector<int> path;

        //递归里面没有处理root节点
        path.push_back(root->val);
        traversal(root, path, targetSum - root->val);
        return result;
    }
};

精简版:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //递归,精简版
    vector<vector<int>> result;
    vector<int> path;

    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return result;
        path.push_back(root->val);//中
        
        if (root->left == nullptr && root->right == nullptr && targetSum == root->val) {
            result.push_back(path); //因为这里需要把当前节点也添加进去所以把中提前了
        }

        pathSum(root->left, targetSum - root->val); //左,每次进去之后都会push一次,pop一次
        pathSum(root->right, targetSum - root->val); //右
        path.pop_back(); //回溯,整个函数值调用了一次push,所以这里调用一次pop

        return result;
    }
};

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

题目链接
文章讲解
视频讲解

在这里插入图片描述

思路:

  1. 后序遍历的最后一个元素,就是根节点;
  2. 用根节点可以将中序遍历分割成左中序和右中序;
  3. 左中序的大小=左后序的大小,以此分割将后序遍历数组分割成左后序和右后序;
  4. 左节点和右节点分别迭代左中序,左后序和右中序,右后序得出。

递归法

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //如果后序数组大小为0,直接返回空节点
    //取后序数组最后一个元素,是根节点,用元素值创建一个二叉树节点
    //如果后序数组大小为1,那么说明是叶子节点了,直接返回根节点
    //在中序数组中找到根节点,获取下标
    //根据上面的下标就可以切割后序数组:左后序数组,右后序数组
    //后序数组舍弃末尾元素
    //左后序数组的大小=左中序数组的大小,以大小来切割中序数组
    //递归传入左中序,左后序和右中序,右后序
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return nullptr;

        //后序数组最后一个元素,就是当前的中间节点
        int rootValue = postorder[postorder.size()-1];
        TreeNode* root = new TreeNode(rootValue);

        //叶子节点
        if (postorder.size() == 1) return root;

        //找到中序遍历的切割垫
        int rootIndex;
        for (rootIndex = 0; rootIndex < inorder.size(); rootIndex++) {
            if (inorder[rootIndex] == rootValue) break;
        }

        //用根节点切割中序数组 [0, rootIndex)
        vector<int> leftInorder (inorder.begin(), inorder.begin()+rootIndex);
        //[rootIndex+1, end) 不要中间节点
        vector<int> rightInorder (inorder.begin()+rootIndex+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 = buildTree(leftInorder, leftPostorder);
        root->right = buildTree(rightInorder, rightPostorder);

        return root;
    }
};

迭代法(高效版)

其实不需要再迭代中重复创建新数组,只获取新的数组下标就可以了。
代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //如果后序数组大小为0,直接返回空节点
    //取后序数组最后一个元素,是根节点,用元素值创建一个二叉树节点
    //如果后序数组大小为1,那么说明是叶子节点了,直接返回根节点
    //在中序数组中找到根节点,获取下标
    //根据上面的下标就可以切割后序数组:左后序数组,右后序数组
    //后序数组舍弃末尾元素
    //左后序数组的大小=左中序数组的大小,以大小来切割中序数组
    //递归传入左中序,左后序和右中序,右后序

    //[inorderBegin,inorderEnd)  [postorderBegin,postorderEnd)
    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 rootIndex;
        //在[inorderBegin,inorderEnd)内遍历
        for (rootIndex = inorderBegin; rootIndex < inorderEnd; rootIndex++) {
            if (inorder[rootIndex] == rootValue) break;
        }

        // 切割中序数组
        // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = rootIndex;
        // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = rootIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割后序数组
        // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
        int leftPostorderBegin = postorderBegin;
        int leftPostorderEnd = postorderBegin + leftInorderEnd - leftInorderBegin; //终止位置是 需要加上 中序区间的大小size
        // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
        int rightPostorderBegin = leftPostorderEnd;
        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 nullptr;

        int rootValue = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(rootValue);

        // 左闭右开的原则
        return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }
};

相关题目:105.从前序与中序遍历序列构造二叉树

题目链接

思路是一样的,只是变成前序的第一个元素是根节点。

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:

    TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd) {
        if (preorderBegin == preorderEnd) return nullptr;
        int rootValue = preorder[preorderBegin]; //这里要用preorderBegin
        TreeNode* root = new TreeNode(rootValue);

        //子节点
        if (preorderEnd - preorderEnd == 1) return root;

        int rootIndex;
        //从inorderBegin遍历到inorderEnd
        for (rootIndex = inorderBegin; rootIndex < inorderEnd; rootIndex++) {
            if (inorder[rootIndex] == rootValue) break;
        }

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

        //分割前序数组
        //左前序数组
        int leftPreorderBegin = preorderBegin + 1;
        int lefttPreorderEnd = leftPreorderBegin + (leftInorderEnd - leftInorderBegin);
        //右前序数组
        int rightPreorderBegin = lefttPreorderEnd;
        int rightPreorderEnd = preorderEnd;

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

        return root; 

    }

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

总结

今天主要是学习了:
1.对二叉树的题目又有了更深入的理解,前两道基本看完题目也有个大概思路,看完视频之后也可以自己写出来代码了。
2.第三道题细节很多,需要结合debug来想清楚里面的细节。

明天继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值