Leecode Hot100 刷题笔记本-二叉树(C++版)

113 阅读16分钟
  1. 22. 括号生成 中等
  2. 94. 二叉树的中序遍历 简单
  3. 96. 不同的二叉搜索树 中等
  4. 98. 验证二叉搜索树 中等
  5. 101. 对称二叉树 简单
  6. 104. 二叉树的最大深度 简单
  7. 105. 从前序与中序遍历序列构造二叉树 中等
  8. 114. 二叉树展开为链表 中等
  9. 124. 二叉树中的最大路径和 困难
  10. 226. 翻转二叉树 简单
  11. 236. 二叉树的最近公共祖先 中等
  12. 297. 二叉树的序列化与反序列化 困难
  13. 538. 把二叉搜索树转换为累加树 中等
  14. 543. 二叉树的直径 简单
  15. 617. 合并二叉树 简单

22. 括号生成

Screen Shot 2023-08-04 at 4.51.11 PM.png

  • 初始时定义序列的左括号数量lc 和右括号数量rc都为0
  • 如果 lc < n,左括号的个数小于n,则在当前序列str后拼接左括号
  • 如果 rc < n && lc > rc , 右括号的个数小于左括号的个数,则在当前序列str后拼接右括号
  • lc == n && rc == n 时,将当前合法序列str加入答案数组res
class Solution {
public:
    vector<string> res; //记录答案 
    vector<string> generateParenthesis(int n) {
        dfs(n , 0 , 0, "");
        return res;
    }
    void dfs(int n ,int lc, int rc ,string str)
    {
        if( lc == n && rc == n) res.push_back(str);   
        else
        {
            if(lc < n) dfs(n, lc + 1, rc, str + "(");
            if(rc < n && lc > rc) dfs(n, lc, rc + 1, str + ")");
        }
    }
};    
  • 时间复杂度: Screen Shot 2023-08-04 at 9.22.20 PM.png

94. 二叉树的中序遍历

Screen Shot 2023-08-04 at 8.15.31 AM.png

解法1: 递归
  • 定义一个vector容器
class Solution {
public:
    void inorder(TreeNode* root, vector<int>& res) {
        if (!root) {
            return;
        }
        inorder(root->left, res);
        res.push_back(root->val);
        inorder(root->right, res);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        inorder(root, res);
        return res;
    }
};
  • 时间复杂度:O(n),其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次
  • 空间复杂度:O(n)。空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别
解法2: 迭代
  • 利用栈的先进后出
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        while (root != nullptr || !stk.empty()) {
            while (root != nullptr) {
                stk.push(root);
                root = root->left;
            }
            root = stk.top();
            stk.pop();
            res.push_back(root->val);
            root = root->right;
        }
        return res;
    }
};
  • 时间复杂度:O(n),其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次
  • 空间复杂度:O(n)。空间复杂度取决于栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别
解法3: Morris 中序遍历
  • Morris 遍历算法是另一种遍历二叉树的方法,它能将非递归的中序遍历空间复杂度降为 O(1)
  • 定义一个vector容器 Screen Shot 2023-08-04 at 9.18.09 AM.png

96. 不同的二叉搜索树

Screen Shot 2023-07-31 at 10.22.52 AM.png

解法1: 动态规划

G(n)的递归公式: Screen Shot 2023-07-31 at 10.45.27 AM.png

class Solution {
public:
    int numTrees(int n) {
        vector<int> G(n + 1, 0);
        G[0] = 1;
        G[1] = 1;

        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= i; ++j) {
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    }
};
  • 时间复杂度: O(n2),其中 nn 表示二叉搜索树的节点个数。G(n) 函数一共有 n 个值需要求解,每次求解需要 O(n) 的时间复杂度,因此总时间复杂度为 O(n2)
  • 空间复杂度: O(n). 我们需要 O(n) 的空间存储 G 数组
解法2: 数学公式

Screen Shot 2023-07-31 at 10.46.02 AM.png

class Solution {
public:
    int numTrees(int n) {
        long long C = 1;
        for (int i = 0; i < n; ++i) {
            C = C * 2 * (2 * i + 1) / (i + 2);
        }
        return (int)C;
    }
};
  • 时间复杂度 : O(n), 其中 n 表示二叉搜索树的节点个数。我们只需要循环遍历一次即可
  • 空间复杂度 : O(1), 我们只需要常数空间存放若干变量。

98. 验证二叉搜索树

Screen Shot 2023-08-04 at 4.33.50 PM.png

解法1: 递归
  • 设计一个递归函数 helper(root, lower, upper) 来递归判断
  • 在递归调用左子树时,我们需要把上界 upper 改为 root.val,即调用 helper(root.left, lower, root.val)
  • 递归调用右子树时,我们需要把下界 lower 改为 root.val,即调用 helper(root.right, root.val, upper)
class Solution {
public:
    bool helper(TreeNode* root, long long lower, long long upper) {
        if (root == nullptr) {
            return true;
        }
        if (root -> val <= lower || root -> val >= upper) {
            return false;
        }
        return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
    }
    bool isValidBST(TreeNode* root) {
        return helper(root, LONG_MIN, LONG_MAX);
    }
};
  • 时间复杂度: O(n), 其中 n 为二叉树的节点个数。在递归调用的时候二叉树的每个节点最多被访问一次,因此时间复杂度为 O(n)
  • 空间复杂度: O(n), 这里的空间复杂度和递归使用的栈空间有关, 这里递归层数不超过 n,故渐进空间复杂度为 O(n), 即二叉树的高度。最坏情况下二叉树为一条链,树的高度为 n ,递归最深达到 n 层,故最坏情况下空间复杂度为 O(n)
解法2: 中序遍历
  • 利用栈
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        stack<TreeNode*> stack;
        long long inorder = (long long)INT_MIN - 1;

        while (!stack.empty() || root != nullptr) {
            while (root != nullptr) {
                stack.push(root);
                root = root -> left;
            }
            root = stack.top();
            stack.pop();
            // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
            if (root -> val <= inorder) {
                return false;
            }
            inorder = root -> val;
            root = root -> right;
        }
        return true;
    }
};
  • 时间复杂度: O(n), 其中 n 为二叉树的节点个数。二叉树的每个节点最多被访问一次,因此时间复杂度为 O(n)
  • 空间复杂度: O(n), 其中 n 为二叉树的节点个数。栈最多存储 n 个节点,因此需要额外的 O(n) 的空间

101. 对称二叉树

Screen Shot 2023-07-31 at 8.37.38 AM.png

解法1: 递归
class Solution {
public:
    bool check(TreeNode *p, TreeNode *q) {
        if (!p && !q) return true;
        if (!p || !q) return false;
        return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
    }

    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};
  • 时间复杂度: O(n),遍历整棵树
  • 空间复杂度: 这里的空间复杂度和递归使用的栈空间有关, 这里递归层数不超过 n,故渐进空间复杂度为 O(n)
解法2: 迭代
class Solution {
public:
    bool check(TreeNode *u, TreeNode *v) {
        queue <TreeNode*> q;
        q.push(u); q.push(v);
        while (!q.empty()) {
            u = q.front(); q.pop();
            v = q.front(); q.pop();
            if (!u && !v) continue;
            if ((!u || !v) || (u->val != v->val)) return false;

            q.push(u->left); 
            q.push(v->right);

            q.push(u->right); 
            q.push(v->left);
        }
        return true;
    }

    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};
  • 时间复杂度: O(n),遍历整棵树
  • 空间复杂度: 这里需要用一个队列来维护节点,每个节点最多进队一次,出队一次,队列中最多不会超过 n 个点,故渐进空间复杂度为 O(n)

104. 二叉树的最大深度

解法1: 深度优先搜索(递归)

Screen Shot 2023-07-30 at 6.52.15 PM.png

解法1: 深度优先搜索
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};
  • 时间复杂度: O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次
  • 空间复杂度: O(height),其中 height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度
解法2: 广度优先搜索
  • 队列
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        queue<TreeNode*> Q;
        Q.push(root);
        int ans = 0;
        while (!Q.empty()) {
            int sz = Q.size();
            while (sz > 0) {
                TreeNode* node = Q.front();
                Q.pop();
                if (node->left) Q.push(node->left);
                if (node->right) Q.push(node->right);
                sz -= 1;
            }d
            ans += 1;
        } 
        return ans;
    }
};
  • 时间复杂度: O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次
  • 空间复杂度: 此方法空间的消耗取决于队列存储的元素数量,其在最坏情况下会达到 O(n)

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

Screen Shot 2023-08-04 at 9.25.09 PM.png

解法1: 递归

  • 构建哈希表, 存放[中序遍历元素, 索引]
  • 定义递归函数, 递归停止条件是preorder的left index> preorder的right index
class Solution {
private:
    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return nullptr;
        }
        
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};
  • 时间复杂度: O(n). 其中 n 是树中的节点个数
  • 空间复杂度: O(n). 除去返回的答案需要的 O(n) 空间之外,我们还需要使用 O(n) 的空间存储哈希映射,以及 O(h)(其中 h 是树的高度)的空间表示递归时栈空间。这里 h<nh<n,所以总空间复杂度 为 O(n)

114. 二叉树展开为链表

Screen Shot 2023-07-31 at 11.01.50 AM.png

  • 新建一个List, 放二叉树的节点
  • 前序遍历二叉树, 将节点存放于list中
  • 遍历list元素, 重新生成二叉树

解法1: 前序遍历(递归)

class Solution {
public:
    void flatten(TreeNode* root) {
        vector<TreeNode*> l;
        preorderTraversal(root, l);
        int n = l.size();
        for (int i = 1; i < n; i++) {
            TreeNode *prev = l.at(i - 1), *curr = l.at(i);
            prev->left = nullptr;
            prev->right = curr;
        }
    }

    void preorderTraversal(TreeNode* root, vector<TreeNode*> &l) {
        if (root != NULL) {
            l.push_back(root);
            preorderTraversal(root->left, l);
            preorderTraversal(root->right, l);
        }
    }
};
  • 时间复杂度: O(n),其中 n 是二叉树的节点数。前序遍历的时间复杂度是 O(n),前序遍历之后,需要对每个节点更新左右子节点的信息,时间复杂度也是 O(n)
  • 空间复杂度: O(n). 其中 n 是二叉树的节点数。空间复杂度取决于栈(递归调用栈或者迭代中显性使用的栈)和存储前序遍历结果的列表的大小,栈内的元素个数不会超过 n,前序遍历列表中的元素个数是 n

解法2: 前序遍历(迭代)

  • 定义一个队列, 和一个List.
  • 开始迭代, 利用栈来前序遍历二叉树, 并将节点依次存入
  • 遍历list元素, 重新生成二叉树
class Solution {
public:
    void flatten(TreeNode* root) {
        auto v = vector<TreeNode*>();
        auto stk = stack<TreeNode*>();
        TreeNode *node = root;
        while (node != nullptr || !stk.empty()) {
            while (node != nullptr) {
                v.push_back(node);
                stk.push(node);
                node = node->left;
            }
            node = stk.top(); stk.pop();
            node = node->right;
        }
        int size = v.size();
        for (int i = 1; i < size; i++) {
            auto prev = v.at(i - 1), curr = v.at(i);
            prev->left = nullptr;
            prev->right = curr;
        }
    }
};
  • 时间复杂度: O(n), 其中 n 是二叉树的节点数。前序遍历的时间复杂度是 O(n), 前序遍历之后,需要对每个节点更新左右子节点的信息,时间复杂度也是 O(n)
  • 空间复杂度: O(n), 其中 n 是二叉树的节点数。空间复杂度取决于栈(递归调用栈或者迭代中显性使用的栈)和存储前序遍历结果的列表的大小,栈内的元素个数不会超过 n,前序遍历列表中的元素个数是 n

解法3: 前序遍历和展开同步进行

  • 借用栈, 迭代实现前序遍历
  • 定义两个二叉树的节点,一个存放当前节点cur, 一个存放之前的节点pre
  • pre->right = cur
class Solution {
public:
    void flatten(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        auto stk = stack<TreeNode*>();
        stk.push(root);
        TreeNode *prev = nullptr;
        while (!stk.empty()) {
            TreeNode *curr = stk.top(); stk.pop();
            if (prev != nullptr) {
                prev->left = nullptr;
                prev->right = curr;
            }
            TreeNode *left = curr->left, *right = curr->right;
            if (right != nullptr) {
                stk.push(right);
            }
            if (left != nullptr) {
                stk.push(left);
            }
            prev = curr;
        }
    }
};
  • 时间复杂度: O(n), 其中 nn 是二叉树的节点数。前序遍历的时间复杂度是 O(n),前序遍历的同时对每个节点更新左右子节点的信息,更新子节点信息的时间复杂度是 O(1),因此总时间复杂度是O(n)
  • 空间复杂度: O(n), 其中 n 是二叉树的节点数。空间复杂度取决于栈的大小,栈内的元素个数不会超过 n

解法3: 寻找前驱节点

class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode *curr = root;
        while (curr != nullptr) {
            if (curr->left != nullptr) {
                auto next = curr->left;
                auto predecessor = next;
                while (predecessor->right != nullptr) {
                    predecessor = predecessor->right;
                }
                predecessor->right = curr->right;
                curr->left = nullptr;
                curr->right = next;
            }
            curr = curr->right;
        }
    }
};
  • 时间复杂度: O(n), 其中 n 是二叉树的节点数。展开为单链表的过程中,需要对每个节点访问一次,在寻找前驱节点的过程中,每个节点最多被额外访问一次
  • 空间复杂度: O(1)

124. 二叉树中的最大路径和

Screen Shot 2023-08-31 at 3.42.27 PM.png

解法1: 递归
  • 维护两个变量, 一个是每个节点最大贡献值, 一个是路径和
  • 每个节点最大贡献值取决于该节点的值与该节点左右子节点的最大贡献值
  • 如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和
  • 维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和
class Solution {
private:
    int maxSum = INT_MIN;

public:
    int maxGain(TreeNode* node) {
        if (node == nullptr) {
            return 0;
        }
        
        // 递归计算左右子节点的最大贡献值
        // 只有在最大贡献值大于 0 时,才会选取对应子节点
        int leftGain = max(maxGain(node->left), 0);
        int rightGain = max(maxGain(node->right), 0);

        // 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
        int priceNewpath = node->val + leftGain + rightGain;

        // 更新答案
        maxSum = max(maxSum, priceNewpath);

        // 返回节点的最大贡献值
        return node->val + max(leftGain, rightGain);
    }

    int maxPathSum(TreeNode* root) {
        maxGain(root);
        return maxSum;
    }
};
  • 时间复杂度: O(N), N 为二叉树节点个数, 每个节点访问不超过两次
  • 空间复杂度: O(N), 空间复杂度主要取决于递归调用层数,最大层数等于二叉树的高度,最坏情况下,二叉树的高度等于二叉树中的节点个数。

226. 翻转二叉树

Screen Shot 2023-07-30 at 7.48.08 PM.png

解法1: 深度优先搜索

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr) {
            return nullptr;
        }
        TreeNode* left = invertTree(root->left);
        TreeNode* right = invertTree(root->right);
        root->left = right;
        root->right = left;
        return root;
    }
};
  • 时间复杂度: O(N),其中 N 为二叉树节点的数目。我们会遍历二叉树中的每一个节点,对每个节点而言,我们在常数时间内交换其两棵子树
  • 空间复杂度: O(N)。使用的空间由递归栈的深度决定,它等于当前节点在二叉树中的高度。在平均情况下,二叉树的高度与节点个数为对数关系,即 O(log⁡N)O(logN)。而在最坏情况下,树形成链状,空间复杂度为 O(N)

236. 二叉树的最近公共祖先

Screen Shot 2023-08-04 at 10.21.48 AM.png

解法1: 递归

class Solution {
public:
    TreeNode* ans;
    bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return false;
        bool lson = dfs(root->left, p, q);
        bool rson = dfs(root->right, p, q);
        if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
            ans = root;
        } 
        return lson || rson || (root->val == p->val || root->val == q->val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};
  • 时间复杂度: O(N). 其中 N 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,因此时间复杂度为 O(N)
  • 空间复杂度: O(N). 其中 N 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N)

解法2: 存储父节点

  • 用哈希表存储所有节点的父节点,然后我们就可以利用节点的父节点信息从 p 结点开始不断往上跳,并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,那么这个节点就是我们要找的最近公共祖先。
  • 从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
  • 从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
  • 同样,我们再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。
class Solution {
public:
    unordered_map<int, TreeNode*> fa;
    unordered_map<int, bool> vis;
    void dfs(TreeNode* root){
        if (root->left != nullptr) {
            fa[root->left->val] = root;
            dfs(root->left);
        }
        if (root->right != nullptr) {
            fa[root->right->val] = root;
            dfs(root->right);
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        fa[root->val] = nullptr;
        dfs(root);
        while (p != nullptr) {
            vis[p->val] = true;
            p = fa[p->val];
        }
        while (q != nullptr) {
            if (vis[q->val]) return q;
            q = fa[q->val];
        }
        return nullptr;
    }
};
  • 时间复杂度: O(N). 其中 N 是二叉树的节点数。二叉树的所有节点有且只会被访问一次,从 p 和q 节点往上跳经过的祖先节点个数不会超过 N,因此总的时间复杂度为 O(N)
  • 空间复杂度: O(N). 其中 N 是二叉树的节点数。递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N,因此空间复杂度为 O(N),哈希表存储每个节点的父节点也需要O(N) 的空间复杂度,因此最后总的空间复杂度为 O(N)

297. 二叉树的序列化与反序列化

Screen Shot 2023-08-31 at 4.17.13 PM.png

解法1: 深度优先搜索+先序遍历
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root==nullptr){
            return "#";
        }
        return to_string(root->val) + ' ' + serialize(root->left) + ' ' + serialize(root->right);
    }

    TreeNode* mydeserialize(istringstream &ss ){
        string tmp;
        ss>>tmp;
        if(tmp=="#"){
            return nullptr;
        }
        TreeNode* node = new TreeNode(stoi(tmp));
        node->left = mydeserialize(ss);
        node->right = mydeserialize(ss);
        return node;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        istringstream ss(data);
        return mydeserialize(ss);
    }
};

538. 把二叉搜索树转换为累加树

Screen Shot 2023-08-04 at 11.30.04 AM.png

解法1: 反序中序遍历

  • 右->根->左
  • 递归
class Solution {
public:
    int sum = 0;

    TreeNode* convertBST(TreeNode* root) {
        if (root != nullptr) {
            convertBST(root->right);
            sum += root->val;
            root->val = sum;
            convertBST(root->left);
        }
        return root;
    }
};
  • 时间复杂度: O(N). 其中 N 是二叉搜索树的节点数。每一个节点恰好被遍历一次
  • 空间复杂度: O(N). 为递归过程中栈的开销,平均情况下为 O(logN),最坏情况下树呈现链状,为 O(N)

解法2: Morris 遍历法

  • 定义一个函数找到当前节点的右子树最左节点 Screen Shot 2023-08-04 at 11.57.12 AM.png
class Solution {
public:
    TreeNode* getSuccessor(TreeNode* node) {
        TreeNode* succ = node->right;
        while (succ->left != nullptr && succ->left != node) {
            succ = succ->left;
        }
        return succ;
    }

    TreeNode* convertBST(TreeNode* root) {
        int sum = 0;
        TreeNode* node = root;

        while (node != nullptr) {
            if (node->right == nullptr) {
                sum += node->val;
                node->val = sum;
                node = node->left;
            } else {
                TreeNode* succ = getSuccessor(node);
                if (succ->left == nullptr) {
                    succ->left = node;
                    node = node->right;
                } else {
                    succ->left = nullptr;
                    sum += node->val;
                    node->val = sum;
                    node = node->left;
                }
            }
        }

        return root;
    }
};
  • 时间复杂度: O(N). 其中 N 是二叉搜索树的节点数。每一个节点恰好被遍历一次
  • 空间复杂度: O(1). 只操作已经存在的指针(树的空闲指针),因此只需要常数的额外空间。

543. 二叉树的直径

Screen Shot 2023-07-30 at 7.09.56 PM.png

深度优先搜索

class Solution {
    int ans;
    int depth(TreeNode* rt){
        if (rt == NULL) {
            return 0; // 访问到空节点了,返回0
        }
        int L = depth(rt->left); // 左儿子为根的子树的深度
        int R = depth(rt->right); // 右儿子为根的子树的深度
        ans = max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans
        return max(L, R) + 1; // 返回该节点为根的子树的深度
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        ans = 1;
        depth(root);
        return ans - 1;
    }
};
  • 时间复杂度: O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次
  • 空间复杂度: O(Height),其中 Height 为二叉树的高度, 由于递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,而递归的深度显然为二叉树的高度,并且每次递归调用的函数里又只用了常数个变量, 所以所需空间复杂度为 O(Height)

617. 合并二叉树

Screen Shot 2023-07-30 at 5.08.17 PM.png

解法1: 深度优先搜索(递归)

记忆要点:

  • 递归算法
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        merged->left = mergeTrees(t1->left, t2->left);
        merged->right = mergeTrees(t1->right, t2->right);
        return merged;
    }
};
  • 时间复杂度:O(min(m,n)), 其中 m 和 n 分别是两个二叉树的节点个数
  • 空间复杂度:O(min(m,n)), 其中 m 和 n 分别是两个二叉树的节点个数
解法2: 广度优先搜索

记忆要点:

  • 广度遍历
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        auto q = queue<TreeNode*>();
        auto queue1 = queue<TreeNode*>();
        auto queue2 = queue<TreeNode*>();
        q.push(merged);
        queue1.push(t1);
        queue2.push(t2);
        while (!queue1.empty() && !queue2.empty()) {
            auto node = q.front(), node1 = queue1.front(), node2 = queue2.front();
            q.pop();
            queue1.pop();
            queue2.pop();
            auto left1 = node1->left, left2 = node2->left, right1 = node1->right, right2 = node2->right;
            if (left1 != nullptr || left2 != nullptr) {
                if (left1 != nullptr && left2 != nullptr) {
                    auto left = new TreeNode(left1->val + left2->val);
                    node->left = left;
                    q.push(left);
                    queue1.push(left1);
                    queue2.push(left2);
                } else if (left1 != nullptr) {
                    node->left = left1;
                } else if (left2 != nullptr) {
                    node->left = left2;
                }
            }
            if (right1 != nullptr || right2 != nullptr) {
                if (right1 != nullptr && right2 != nullptr) {
                    auto right = new TreeNode(right1->val + right2->val);
                    node->right = right;
                    q.push(right);
                    queue1.push(right1);
                    queue2.push(right2);
                } else if (right1 != nullptr) {
                    node->right = right1;
                } else {
                    node->right = right2;
                }
            }
        }
        return merged;
    }
};
  • 时间复杂度:O(min(m,n)), 其中 m 和 n 分别是两个二叉树的节点个数
  • 空间复杂度:O(min(m,n)), 其中 m 和 n 分别是两个二叉树的节点个数