代码随想录第18天 | 找树左下角的值 、 路径总和 、 从中序与后序遍历序列构造二叉树

一、前言

参考文献:代码随想录

今天是周六,还是二叉树,不过这次的题目又相对于前两天又有了一些提升。所以就慢慢来体会递归的细节吧!

二、找树左下角的值  

1、思路:

这一题的第一思路想到的是=有两种,第一种递归法,第二种迭代法(利用队列来实现层序遍历),但是我尝试递归后,并不能很快的解答出来,因为我理解的是出自最左子树上的最左边的节点的数值(想法太单纯了)

得到了以下代码:

class Solution {
private:
    int result;
    void getLeftBotten(TreeNode* node) {
        if (node == NULL) {
            return;
        }
        getLeftBotten(node->left);
        getLeftBotten(node->right);
        if (node->left != NULL && node->left->left == NULL) {
            result = node->left->val;
        }
        return;
    }
public:
    int findBottomLeftValue(TreeNode* root) {
        if (root->left == NULL && root->right == NULL) {
            return root->val;
        }
        getLeftBotten(root);
        return result;
    }
};

这个就是一个简单的后序遍历,然后在中序遍历上做点手脚就OK了。但是这样并不能解决题目的意思,因为我永远找的是最后一个出现的左叶子节点。

2、迭代法:

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        // 常规模板
        // 利用队列来实现层序遍历
        int result = 0;
        queue<TreeNode*> que;
        if (root == NULL) return 0;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();

            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                // 遍历到每一层的第一位(最左边的一位)时,更新一下result
                if (i == 0) {
                    result = node->val;
                }
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return result;
    }
};

迭代法的原理比较简单,我这里就不做过多的说明了。

3、递归法:

这里的递归方法运用了回溯,所以比较抽象。

但是经过我在群里和大佬的讨论,已经对回溯这种抽象的概念有了一定的理解了,就是把一个值拿用完一次之后,又退回到原来的值去用一次,大概就是这样。

本题目的解法的递归思路如下:

(1)返回值为void,没有需要返回的,参数为一个深度和TreeNode的指针,为什么要有深度呢?因为这里的最底部光靠单纯徐遍历是无法确定的,必须要有一个深度参数来确定是否为最底层。

(2)终止条件为是否非叶子节点,为叶子节点就需要更新depth的值,更新返回值;

(3)单层递归逻辑比较简单,采用的是前序遍历(后序,中序都可以,只需要左子树先操作即可)。

递归代码如下:

class Solution {
    int result;
    int depthMax = INT_MIN;
    void getLeftBotton(TreeNode* node, int depth) {
        if (node->left == NULL && node->right == NULL) {
            if (depth > depthMax) {
                depthMax = depth;
                result = node->val;
            }
        }

        if (node->left) {
            depth++;
            getLeftBotton(node->left, depth);
            depth--; // 回溯过程,给右子树用
        }
        if (node->right) {
            depth++;
            getLeftBotton(node->right, depth);
            depth--;
        }
        return;
    }
public:
    int findBottomLeftValue(TreeNode* root) {
        getLeftBotton(root, 0);
        return result;
    }
};

 三、路经总和

1、思路:

这个题目还是使用递归,但是对于我来说比较麻烦,我没有啥思路,我写了一个错误的版本,但是没有通过,我就去问gpt教授了,gpt教授给了我一个方案,我总结如下:

(1)返回值,传入参数:

返回bool类型,传入的参数为TreeNode、sum(统计和)、tagetSum(比较参数)

(2)终止条件:

遍历到空节点了就返回false,sum == targetSum即返回true

(3)单层递归:

左右同时递归,然后并起来,如果有就true,就返回true。

2、代码:

class Solution {
private:
    bool findSum(TreeNode* node, int sum, int targetSum) {
            if (node == NULL) {
                return false;
            }
            sum += node->val;
            if (node->left == NULL && node->right == NULL) {
                return sum == targetSum;
            }
            // 分开递归,其中有一个为真,就返回真!
            return findSum(node->left, sum, targetSum) || findSum(node->right, sum, targetSum);
        }
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        return  findSum(root, 0, targetSum);
    }
};

 看了一眼卡哥写的,感觉也很精妙,整体思路如下:

(1)使用的sum初始化为targetSum,在每次递归的时候就是剪掉当前的值,如果到了根节点并且sum==0的话就说明存在路径,可以返回false

代码如下:

class Solution {
private:
    bool findSum(TreeNode* node, int sum) {
        if(node == NULL) {
            return false;
        } else if (node->left == NULL && node->right == NULL && sum == 0) {
            return true;
        }


        if (node->left) {
            sum -= node->left->val;
            if (findSum(node->left, sum)) return true;
            sum += node->left->val; // 回溯,hh
        }
        if (node->right) {
            sum -= node->right->val;
            if (findSum(node->right, sum)) return true;
            sum += node->right->val;
        }
        return false;
    }
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == NULL) return false;
        return findSum(root, targetSum - root->val);
    }
};

简化之后和gpt教授的内容差不多。

四、路径总和||:

1、思路:

思路差不多,但是多了一个返回所有路径的要求,错误代码如下,我的第一思考。。

class Solution {
private:
    vector<vector<int>> result;
    void getSums(TreeNode* node, vector<int>& vec, int sum) {
        if (node == NULL) return;
        if (sum == 0 && node->left == NULL && node->right == NULL) {
            result.push_back(vec);
        }
        
        if (node->left) {
            vector<int> leftVec = vec;
            leftVec.push_back(node->val);
            getSums(node->left, leftVec, sum - node->val);
        }

        if (node->right) {
            vector<int> rightVec = vec;
            rightVec.push_back(node->val);
            getSums(node->right, rightVec, sum - node->val);
        }
    }
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<int> vec;
        getSums(root, vec, targetSum);
        return result;
    }
};

真的是递归、回溯傻傻分不清楚。。

我去看了gpt教授的思路后,我有不禁感叹,我还是太菜了!

思路如下:

(1)返回值为void,传入参数有三个

void getSums(TreeNode* node, vector<int>& vec, int sum)

一个是节点,一个是记录路径上的值,最后一个是用来比较是否成功的。

(2)终止条件为

if (sum == node->val && node->left == NULL && node->right == NULL) {
            result.push_back(vec);
        }

这里有一个小细节,sum == node->val因为在这里时已经把当前节点的值存入到了vec当中,只需要判断最后的sum是否减去node->val等于0,即可保存当前路径了。

(3)单层递归如下

 getSums(node->left, vec, sum - node->val);
            getSums(node->right, vec, sum - node->val);
        }
        vec.pop_back();// 回溯

伴随着两个数据的回溯,确实是相当烧脑了。

2、整体代码如下:

class Solution {
private:
    vector<vector<int>> result;
    void getSums(TreeNode* node, vector<int>& vec, int sum) {
        if (node == NULL) return;
        vec.push_back(node->val);
        if (sum == node->val && node->left == NULL && node->right == NULL) {
            result.push_back(vec);
        } else {
            getSums(node->left, vec, sum - node->val);
            getSums(node->right, vec, sum - node->val);
        }
        vec.pop_back();// 回溯
    }
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<int> vec;
        getSums(root, vec, targetSum);
        return result;
    }
};

五、从中序与后序遍历序列构造二叉树 

1、思路:

确认过眼神,这是我不会的题。。虽然做过一次,但是已经没有任何印象了,这里的思路很难想出来。只有去看卡哥视频了。

这题卡个总结了如下六点:

(1)如果数组大小为零,那就是空节点了。

(2)如果不为空,那就取后续数组的最后一个元素为根节点。

(3)找到后续元素在中序数组中的位置,进行写个。

(4)优先切割中序数组

(5)切割后序数组

(6)递归左右区间

这样卡哥一总结下来就觉得很清晰了,我来一步一步实现。

2、代码如下:

class Solution {
private:
    TreeNode* traversal(vector<int> &inorder, vector<int>& postorder) {
        // 第一步
        if (postorder.size() == 0) return NULL;
        // 第二步
        int postVal = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(postVal);
        // 叶子节点
        if (postorder.size() == 1) return root;
        // 第三步
        int delimiterIndex;
        for (int i = 0; i < inorder.size(); i++) {
            if (inorder[i] == postVal) {
                delimiterIndex = i;
                break;
            }
        }
        // 第四步,切割中序数组
        // 确定区间,采用左闭右开的区间[0, dilimiterIndex)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        // [delimiterIndex, end)
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());

        // 第五步,切割后序数组
        // 1、舍弃最后一个元素
        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);
    }
};

六、中序遍历和前序遍历构成二叉树

1、思路:

这题和上题可以说一模一样,只需要修改一下细节即可

2、代码如下:

class Solution {
    private:
    TreeNode* traversal(vector<int> &inorder, vector<int>& preorder) {
        // 第一步
        if (preorder.size() == 0) return NULL;
        // 第二步
        int preVal = preorder[0];
        TreeNode* root = new TreeNode(preVal);
        // 叶子节点
        if (preorder.size() == 1) return root;
        // 第三步
        int delimiterIndex;
        for (int i = 0; i < inorder.size(); i++) {
            if (inorder[i] == preVal) {
                delimiterIndex = i;
                break;
            }
        }
        // 第四步,切割中序数组
        // 确定区间,采用左闭右开的区间[0, dilimiterIndex)
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        // [delimiterIndex, end)
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());

        // 第五步,切割后序数组
        // 1、舍弃第一个元素
        preorder.erase(preorder.begin());
        // 这里就有一个数学小知识了,左中序数组的长度应该等于左后序数组的长度。所以这就是突破点。
        // 这里我们继续使用左闭右开的区间
        vector<int> leftPerorder(preorder.begin(), preorder.begin() + leftInorder.size());
        vector<int> rightPerorder(preorder.begin() + leftInorder.size(), preorder.end());
        // 第六步递归
        root->left = traversal(leftInorder, leftPerorder);
        root->right = traversal(rightInorder, rightPerorder);
        return root;
    }
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() == 0 || inorder.size() == 0) return 0;
        return traversal(inorder, preorder);
    }
};

今日学习时间:2.5个小时;

leave message:

Each man has own ambition in life, in spite of everything they were determined.

人各有志,终不为次移。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值