【递归,搜索与回溯算法】专题二:二叉树的深搜

📝前言说明:

  • 本专栏主要记录本人递归,搜索与回溯算法的学习以及LeetCode刷题记录,按专题划分
  • 每题主要记录:(1)本人解法 + 本人屎山代码;(2)优质解法 + 优质代码;(3)精益求精,更好的解法和独特的思想(如果有的话)
  • 文章中的理解仅为个人理解。如有错误,感谢纠错

🎬个人简介:努力学习ing
📋本专栏:C++刷题专栏
📋其他专栏:C语言入门基础python入门基础C++学习笔记Linux
🎀CSDN主页 愚润泽

你可以点击下方链接,进行该专题内不同子专题的学习

点击链接开始学习
导论递归 (一)递归 (二)
二叉树的深搜穷举 vs 暴搜 vs 深搜 vs 回溯 vs 剪枝
综合练习(一)综合练习(二)
综合练习(三)综合练习(四)
FloodFill(一)FloodFill(二)
记忆化搜索(一)记忆化搜索(二)

题单汇总链接:点击 → 题单汇总


2331. 计算布尔二叉树的值

题目链接:https://leetcode.cn/problems/evaluate-boolean-binary-tree/description/
在这里插入图片描述

个人解

思路:

  • 子问题:给一个根节点,计算这个布尔二叉树的值并返回
  • 函数体:先递归得到左右布尔二叉子树的值,然后再根据根节点的val进行计算
  • 函数出口:当节点为空 / 叶子节点。为空时,返回 1

屎山代码(通过):

class Solution {
public:
    bool evaluateTree(TreeNode* root) 
    {
        if(root == nullptr) return 1;
        if(root->left == nullptr && root->right == nullptr) return root->val;
        if(root->val == 2)
            return evaluateTree(root->left) || evaluateTree(root->right);
        else
            return evaluateTree(root->left) && evaluateTree(root->right);
    }
};

时间复杂度:O(n),每个节点都要被访问一次
空间复杂度:O(n)


129. 求根节点到叶节点数字之和

题目链接:https://leetcode.cn/problems/sum-root-to-leaf-numbers/description/
在这里插入图片描述


优质解

思路:

  • 用前序遍历,并且把上层信息往下传,当到达递归出口以后,再层层把结果传回来
  • 在前序遍历的过程中,我们可以往左右⼦树传递信息,并且在回溯时得到左右⼦树的返回值。递归函数可以帮我们完成两件事:
    1. 将⽗节点的数字与当前节点的信息整合到⼀起,计算出当前节点的数字,然后传递到下⼀层进⾏递归;
    2. 当遇到叶⼦节点的时候,就不再向下传递信息,⽽是将整合的结果向上⼀直回溯到根节点。

代码:

class Solution {
public:
    int sumNumbers(TreeNode* root, int presum = 0) 
    {
        if(root == nullptr) return 0;
        presum = presum * 10 + root->val;
        if(root->left == nullptr && root->right == nullptr) return presum;
        return sumNumbers(root->left, presum) + sumNumbers(root->right, presum);
    }
};

时间复杂度:O(n)
空间复杂度:O(n)


814. 二叉树剪枝

题目链接:https://leetcode.cn/problems/binary-tree-pruning/description/
在这里插入图片描述

优质解

思路:

我们通过在决策树上分析,模拟递归展开和返回的过程,来抽象出函数头、函数体,递归出口三大条件。

在这里插入图片描述

代码:

class Solution {
public:
    TreeNode* pruneTree(TreeNode* root) 
    {
        if(!root) return nullptr;
        root->left =  pruneTree(root->left);
        root-> right = pruneTree(root->right); // 如果是nullptr则直接剪枝了
        if(!root->left && !root->right && root->val == 0) return nullptr;
        else return root;
    }
};

时间复杂度:O(n)
空间复杂度:O(n)


98. 验证二叉搜索树

题目链接:https://leetcode.cn/problems/validate-binary-search-tree/description/
在这里插入图片描述

个人解

思路:

  • 子问题:给一个根节点,判断这棵树是不是二叉搜索树,并且记录当前数的最小值和最大值(要用long long,因为要比所有int小 / 大)
  • 函数体:判断左右子树是否是二叉搜索树,比较当前节点的值和左右孩子的值
  • 递归出口:节点为空

屎山代码:

class Solution {
public:
    bool isValidBST(TreeNode* root, long long left  = LLONG_MIN, long long right = LLONG_MAX)
    {
        if(root == nullptr) return true;
        long long x = root->val;
        return left < x && x < right &&
            isValidBST(root->left, left, x) &&
            isValidBST(root->right, x, right);
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

当然也可以用中序遍历,因为搜索二叉树的中序遍历一定是一个有序数组!


230. 二叉搜索树中第 K 小的元素

题目链接:https://leetcode.cn/problems/kth-smallest-element-in-a-bst/description/

在这里插入图片描述

个人解

思路:

  • 二叉搜索树的中序遍历一定是一个有序序列,取第 k 个数

屎山代码:

class Solution {
public:
    int ans;
    int cnt = 0;
    void dfs(TreeNode* root, int k)
    {
        if(!root || cnt == k) return;
        dfs(root->left, k);
        cnt++;
        if(cnt == k) ans = root->val;
        dfs(root->right, k);
    }

    int kthSmallest(TreeNode* root, int k) 
    {
        dfs(root, k);
        return ans;
    }
};

时间复杂度:O (H + k)
空间复杂度:O (H)


257. 二叉树的所有路径

题目链接:https://leetcode.cn/problems/binary-tree-paths/description/
在这里插入图片描述

个人解

思路:

// 子问题:给一个根节点,返回所有路径
// 函数体:(根节点 + 左子树所有路径) + (根节点 + 右子树所有路径)
// 递归出口:当节点为空

屎山代码(通过):

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) 
    {
        vector<string> ans;
        if(!root)
            return ans;
        
        string cur = to_string(root->val);
        if(!root->left && !root->right)
        {
            ans.emplace_back(cur);
            return ans;
        }
        
        if(root->left)
        {
            vector<string> ans_left = binaryTreePaths(root->left);
            for(auto &s: ans_left)
            {
                s = cur + "->" + s;
                ans.emplace_back(s);
            }
        }

        if(root->right)
        {
            vector<string> ans_right = binaryTreePaths(root->right);
            for(auto &s: ans_right)
            {
                s = cur + "->" + s;
                ans.emplace_back(s);
            }
        }
        return ans;
    }
};

时间复杂度:O( n 2 n^2 n2)
空间复杂度:O( n 2 n^2 n2)


优质解

思路:
从上往下添加节点,直到添加完根节点就直接把这条路径添加到全局变量ans里。

回溯

利用:全局变量 + 回溯 + 剪枝

  • 全局变量
    • vecotr<string> ans:记录每一条到叶节点后,返回的路径
  • 回溯:只要有递归,就会有回溯(只是有的题用了,有的题没用)
    • 往上回溯的时候,要恢复现场(本题中就是把下一层添加的节点去掉)
  • 恢复现场
    • 本题不用全局变量path恢复现场(因为如果是全局变量,不好修改下层返回的字符串)
    • 用函数参数path恢复现场函数参数在每一个栈帧里都有,而且在每一个函数调用体内是不一样的
    • 所以我们在函数内修改path,不会影响别的栈帧的path,所以函数帮助了我们恢复现场
  • 剪枝
    • 本题剪枝就是:如果发现节点为空就不进去了

代码:

class Solution {
public:
    vector<string> ans;
    void dfs(TreeNode* root, string path)
    {
        path += to_string(root->val);
        if(!root->left && !root->right) 
        {
            ans.emplace_back(path);
            return;
        }
        path += "->";
        if(root->left)
            dfs(root->left, path);
        if(root->right)
            dfs(root->right, path);
    }

    vector<string> binaryTreePaths(TreeNode* root) 
    {
        string path;
        dfs(root, path);
        return ans;
    }
};

时间复杂度:O( n 2 n^2 n2),访问时间O(n),对path的拷贝O(n)
空间复杂度:O( n 2 n^2 n2)


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚润泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值