四、二叉树-下(Binary tree)

news2024/11/27 19:54:06

文章目录

  • 一、算法核心
  • 二、经典例题
      • 1.[226. 翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 2.[101. 对称二叉树](https://leetcode.cn/problems/symmetric-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 3.[222. 完全二叉树的节点个数](https://leetcode.cn/problems/count-complete-tree-nodes/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 4.[110.平衡二叉树](https://leetcode.cn/problems/balanced-binary-tree/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 5.[257. 二叉树的所有路径](https://leetcode.cn/problems/binary-tree-paths/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 6.[404. 左叶子之和](https://leetcode.cn/problems/sum-of-left-leaves/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 7.[513. 找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 8. [112.路经总和](https://leetcode.cn/problems/path-sum/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 9. [113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 9. [113. 路径总和 II](https://leetcode.cn/problems/path-sum-ii/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 10. [106. 从中序与后序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 11. [105. 从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 12. [654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 13. [617. 合并二叉树](https://leetcode.cn/problems/merge-two-binary-trees/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 14. [700.二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 15. [98. 验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 17. [501. 二叉搜索树中的众数](https://leetcode.cn/problems/find-mode-in-binary-search-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 18. [530. 二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析
      • 19. [236. 二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/description/)
        • (1)思想
        • (2)代码
        • (3)复杂度分析

一、算法核心

1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。

二、经典例题

1.226. 翻转二叉树

(1)思想

注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!请添加图片描述

(2)代码
/**
 * 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* invertTree(TreeNode* root) {
        if (root == nullptr) return root;
        swap(root->left, root->right);  // 中
        invertTree(root->left);         // 左
        invertTree(root->right);        // 右
        return root;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

2.101. 对称二叉树

(1)思想

递归三部曲:

  1. 确定递归函数的参数和返回值
    因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

  2. 确定终止条件
    要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
    左节点为空,右节点不为空,不对称,return false
    左不为空,右为空,不对称 return false
    左右都为空,对称,返回true
    此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
    左右都不为空,比较节点数值,不相同就return false
    此时左右节点不为空,且数值也不相同的情况我们也处理了。

  3. 确定单层递归的逻辑
    此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
    比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
    如果左右都对称就返回true ,有一侧不对称就返回false 。

(2)代码
/**
 * 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 compare(TreeNode* left,TreeNode* right) {
        if (left == nullptr && right != nullptr) return false;
        else if (left != nullptr && right == nullptr) return false;
        else if (left == nullptr && right == nullptr) return true;
        else if (left -> val != right -> val) return false;
        else return (compare(left -> left,right -> right))&& compare(left->right, right->left);
    }
    bool isSymmetric(TreeNode* root) {
        if (root == nullptr) return true;
        return compare(root->left, root->right);
    }
};
(3)复杂度分析

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

3.222. 完全二叉树的节点个数

(1)思想
  1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
  2. 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
  3. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
(2)代码
/**
 * 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 getNodesNum(TreeNode* cur) {
        if (cur == NULL) return 0;
        int leftNum = getNodesNum(cur -> left);
        int rightNum = getNodesNum(cur -> right);
        int treeNum = leftNum + rightNum + 1;
        return treeNum;
}
    int countNodes(TreeNode* root) {
        return getNodesNum(root);
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( l o g n ) O(log n) O(logn),算上了递归系统栈占用的空间

4.110.平衡二叉树

(1)思想

那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。

  1. 明确递归函数的参数和返回值
    参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。
  2. 明确终止条件
    递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0
  3. 明确单层递归的逻辑
    如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。
(2)代码
class Solution {
public:
    // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
    int getHeight(TreeNode* node) {
        if (node == NULL) {
            return 0;
        }
        int leftHeight = getHeight(node->left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;
        return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
    }
    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

5.257. 二叉树的所有路径

(1)思想

在这里插入图片描述
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。

  1. 递归函数参数以及返回值
要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
  1. 确定递归终止条件
if (cur->left == NULL && cur->right == NULL) {
    终止处理逻辑
}
  1. 确定单层递归逻辑
(2)代码
/**
 * 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:
    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
        path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中 
        if (cur -> left == nullptr && cur -> right == nullptr) {
            string temp;
            for (int i = 0; i < path.size() - 1; i ++) {
                temp += to_string(path[i]);
                temp += "->";
            }
            temp += to_string(path[path.size() - 1]);
            result.push_back(temp);
            return;
        }
        if (cur->left) { // 左 
            traversal(cur->left, path, result);
            path.pop_back(); // 回溯
        }
        if (cur->right) { // 右
            traversal(cur->right, path, result);
            path.pop_back(); // 回溯
        }
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if (root == nullptr) return result;
        traversal(root,path,result);
        return result;
    }
};
(3)复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n 2 ) O(n^2) O(n2),算上了递归系统栈占用的空间

6.404. 左叶子之和

(1)思想
  1. 确定递归函数的参数和返回值
    判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int
    使用题目中给出的函数就可以了。
  2. 如果遍历到空节点,那么左叶子值一定是0
    注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:
  3. 确定单层递归的逻辑
    当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。
(2)代码
/**
 * 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 sumProcess(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right== NULL) return 0;
        int leftValue = sumOfLeftLeaves(root->left);  //   左
        if (root->left != NULL && root->left->left == NULL && root->left->right == NULL) 
     // 左叶子节点处理逻辑
            leftValue = root->left->val;
        int rightValue = sumOfLeftLeaves(root->right);  // 右
        int sum = leftValue + rightValue;               // 中
        return sum;
    }
    int sumOfLeftLeaves(TreeNode* root) {
        return sumProcess(root);
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

7.513. 找树左下角的值

(1)思想
(2)代码
dfs
class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void traversal(TreeNode* root, int depth) {
        if (root->left == NULL && root->right == NULL) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = root->val;
            }
            return;
        }
        if (root->left) {
            depth++;
            traversal(root->left, depth);
            depth--; // 回溯
        }
        if (root->right) {
            depth++;
            traversal(root->right, depth);
            depth--; // 回溯
        }
        return;
    }
    int findBottomLeftValue(TreeNode* root) {
        traversal(root, 0);
        return result;
    }
};
bfs
/**
 * 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) {
        int result = 0;
        if (root == nullptr) return 0;
        queue<TreeNode*> que;
        que.push(root);
        while (que.size()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                auto temp = que.front();
                que.pop();
                if (i == 0) result = temp -> val; 
                if (temp->left) que.push(temp ->left);
                if (temp->right) que.push(temp ->right);
            }
        }
        return result;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

8. 112.路经总和

(1)思想
  1. 确定递归函数的参数和返回类型
    参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

在这里插入图片描述

图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
2. 确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
递归终止条件代码如下

if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
  1. 确定单层递归的逻辑
    因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
    递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
(2)代码
class Solution {
private:
    bool traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回

        if (cur->left) { // 左
            count -= cur->left->val; // 递归,处理节点;
            if (traversal(cur->left, count)) return true;
            count += cur->left->val; // 回溯,撤销处理结果
        }
        if (cur->right) { // 右
            count -= cur->right->val; // 递归,处理节点;
            if (traversal(cur->right, count)) return true;
            count += cur->right->val; // 回溯,撤销处理结果
        }
        return false;
    }

public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == NULL) return false;
        return traversal(root, sum - root->val);
    }
};
(3)复杂度分析

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( H ) O(H) O(H),其中 H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(log⁡N)。

9. 113. 路径总和 II

(1)思想
(2)代码
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( l o g n ) O(log n) O(logn),算上了递归系统栈占用的空间

9. 113. 路径总和 II

(1)思想

113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!

(2)代码
/**
 * 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>> res;
vector<int> path;
    void tra(TreeNode* root,int count) {
        if (!root -> left && !root ->right && count == 0) {
            res.push_back(path);
            return;
        }
        if (!root -> left && !root ->right)
            return;
        if (root -> left) {
            path.push_back(root -> left -> val);
            count -= root -> left -> val;
            tra(root -> left,count);
            count += root -> left -> val;
            path.pop_back();
        }
         if (root -> right) {
            path.push_back(root -> right -> val);
            count -= root -> right -> val;
            tra(root -> right,count);
            count += root -> right -> val;       
            path.pop_back();
        }
        
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        if (root == nullptr)
            return res;
        path.push_back(root->val);
        tra(root,targetSum - root -> val);
        
        return res;
    }
};
(3)复杂度分析

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( H ) O(H) O(H),其中 H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(log⁡N)。

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

(1)思想

在这里插入图片描述
来看一下一共分几步:
// 中序:左根右
// 后序:左右根
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间

(2)代码
/**
 * 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) {}
 * };
 */
/**
 * 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>& 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 delimiterIndex;
        // 找到中序遍历的切割点
        for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex ++) {
             if (inorder[delimiterIndex] == rootValue) break;
        }
        // 切割中序数组
        // 左闭右开区间:[0, delimiterIndex)
        vector<int> leftInorder(inorder.begin(),inorder.begin() + delimiterIndex);
        vector<int> rightInorder (inorder.begin() + delimiterIndex + 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);
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

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

(1)思想
(2)代码
class Solution {
private:
        TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& preorder, int preorderBegin, int preorderEnd) {
        if (preorderBegin == preorderEnd) return NULL;

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

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

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

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

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

        return root;
    }

public:
    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());
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

12. 654. 最大二叉树

(1)思想

我们用递归函数 construct(nums,left,right)表示对数组 nums中从 nums[left] 到 nums[right]的元素构建一棵树。我们首先找到这一区间中的最大值,记为 num中从 nums[maxValue],这样就确定了根节点的值。随后我们就可以进行递归:

左子树为 construct(nums,left,maxValue−1)

右子树为 construct(nums,maxValue+1,right)

当递归到一个无效的区间(即 left>right)时,便可以返回一棵空的树。

(2)代码
/**
 * 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* constructMaximumBinaryTree(vector<int>& nums) {
        return construct(nums,0,nums.size() - 1);
    }
    TreeNode* construct(const vector<int>&nums,int left,int right) {
        if (left > right) {
            return nullptr;
        }
        int maxValue = left;
        for (int i = left; i <= right; i++) {
            if (nums[i] > nums[maxValue]) {
                maxValue = i;
            }
        }
        TreeNode* node = new TreeNode(nums[maxValue]);
        node->left = construct(nums, left, maxValue - 1);
        node->right = construct(nums, maxValue + 1, right);
        return node;
    }
};
(3)复杂度分析

时间复杂度: O ( n 2 ) O(n^2) O(n2)其中 n 是数组 n u m s nums nums的长度。在最坏的情况下,数组严格递增或递减,需要递归 n n n 层,第 i ( 0 ≤ i < n ) i (0≤i<n) i(0i<n)层需要遍历 n − i n−i ni个元素以找出最大值,总时间复杂度为 O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

13. 617. 合并二叉树

(1)思想
  1. 确定递归函数的参数和返回值:
    首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
  2. 确定终止条件:
    因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。
    反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
  3. 确定单层递归的逻辑:
    单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
(2)代码
/**
 * 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* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if (root1 == nullptr)
            return root2;
        if (root2 == nullptr)
            return root1;
        auto merge = new TreeNode(root1 -> val + root2 -> val);
        merge -> left = mergeTrees(root1 -> left,root2 -> left);
        merge -> right = mergeTrees(root1 -> right,root2 -> right);
        return merge;
    }
};
(3)复杂度分析

时间复杂度: O ( m i n ( m , n ) O(min(m,n) O(min(m,n)
空间复杂度: O ( m i n ( m , n ) O(min(m,n) O(min(m,n),算上了递归系统栈占用的空间

14. 700.二叉搜索树中的搜索

(1)思想

二叉搜索树满足如下性质:

左子树所有节点的元素值均小于根的元素值;
右子树所有节点的元素值均大于根的元素值。
据此可以得到如下算法:

若 root为空则返回空节点;
若 val=root.val,则返回 root;
若 val<root.val,递归左子树;
若 val>root.val,递归右子树。

(2)代码
/**
 * 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* searchBST(TreeNode* root, int val) {
        if (root == nullptr) return nullptr;
        if (root -> val == val) return root;
        TreeNode* result = nullptr;
        if (root -> val < val) result = searchBST(root -> right,val);
        if (root -> val > val) result = searchBST(root -> left,val);
        return result;
    }
};
迭代:
class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        while (root != NULL) {
            if (root->val > val) root = root->left;
            else if (root->val < val) root = root->right;
            else return root;
        }
        return NULL;
    }
};
(3)复杂度分析

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( N ) O(N) O(N),算上了递归系统栈占用的空间

时间复杂度: O ( N ) O(N) O(N)
空间复杂度: O ( 1 ) O(1) O(1),算上了递归系统栈占用的空间

15. 98. 验证二叉搜索树

(1)思想

启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。如果均大于说明这个序列是升序的,整棵树是二叉搜索树,否则不是,下面的代码我们使用栈来模拟中序遍历的过程。

(2)代码
/**
 * 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:
long long maxVal = LONG_MIN; 
    bool isValidBST(TreeNode* root) {
        if (root == nullptr) return true;
        bool left = isValidBST(root -> left);
        if (maxVal < root -> val) maxVal = root -> val;
        else return false;
        bool right = isValidBST(root -> right);
        return left && right;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

17. 501. 二叉搜索树中的众数

(1)思想
  1. 这个树都遍历了,用map统计频率
  2. 把统计的出来的出现频率(即map中的value)排个序
  3. 取前面高频的元素
(2)代码
/**
 * 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<int> res;
    unordered_map<int,int> mp;
    void process(TreeNode* root) {
        if (root == nullptr) return ;
        process(root -> left);
        mp[root -> val] ++;
        process(root -> right);
    }
    bool static cmp(const pair<int,int>& a,const pair<int,int> &b) {
        return a.second > b.second;
    }
    vector<int> findMode(TreeNode* root) {
        process(root);
        vector<pair<int, int>> vec(mp.begin(), mp.end());
        sort(vec.begin(), vec.end(), cmp); // 给频率排个序
        res.push_back(vec[0].first);
        for (int i = 1; i < vec.size(); i++) {
            if (vec[i].second == vec[0].second) res.push_back(vec[i].first);
            else break;
        }
        return res;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

18. 530. 二叉搜索树的最小绝对差

(1)思想

朴素的方法是经过一次中序遍历将值保存在一个数组中再进行遍历求解,我们也可以在中序遍历的过程中用 pre变量保存前驱节点的值,这样即能边遍历边更新答案,不再需要显式创建数组来保存,需要注意的是 pre的初始值需要设置成任意负数标记开头,下文代码中设置为 −1。

(2)代码
/**
 * 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_MAX;
TreeNode* pre = NULL;
    void isBST(TreeNode* root) {
        if (root == nullptr) return;
        isBST(root -> left);
        if (pre != nullptr) {
            result = min(result,root -> val - pre -> val);
        }
        pre = root;
        isBST(root -> right);
    }
    int getMinimumDifference(TreeNode* root) {
        if (root == nullptr) return false;
        isBST(root);
        return result;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

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

(1)思想

遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
那么二叉树如何可以自底向上查找呢?
回溯啊,二叉树回溯的过程就是从低到上。
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。
首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。
即情况一:
在这里插入图片描述

但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q§。 情况二:
在这里插入图片描述

  1. 确定递归函数返回值以及参数
    需要递归函数返回值,来告诉我们是否找到节点q或者p,那么返回值为bool类型就可以了。但我们还要返回最近公共节点,可以利用上题目中返回值是TreeNode * ,那么如果遇到p或者q,就把q者p返回,返回值不为空,就说明找到了q或者p。

  2. 确定终止条件
    遇到空的话,因为树都是空了,所以返回空。
    那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。

  3. 确定单层递归逻辑
    在这里插入图片描述
    在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。

(2)代码
/**
 * 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_MAX;
TreeNode* pre = NULL;
    void isBST(TreeNode* root) {
        if (root == nullptr) return;
        isBST(root -> left);
        if (pre != nullptr) {
            result = min(result,root -> val - pre -> val);
        }
        pre = root;
        isBST(root -> right);
    }
    int getMinimumDifference(TreeNode* root) {
        if (root == nullptr) return false;
        isBST(root);
        return result;
    }
};
(3)复杂度分析

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),算上了递归系统栈占用的空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1070211.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaSE】Synchronized实现原理

我们通常来使用synchronized来保证原子性&#xff0c;保证线程的安全。 但其实synchronized的底层是由一对monitorenter/monitorexit指令实现&#xff0c;每一个对象都有一个监视器&#xff08;monitor&#xff09;&#xff0c;而synchronized是通过对象内部叫监听器&#xff…

11.3 读图举例

一、低频功率放大电路 图11.3.1所示为实用低频功率放大电路&#xff0c;最大输出功率为 7 W 7\,\textrm W 7W。其中 A \textrm A A 的型号为 LF356N&#xff0c; T 1 T_1 T1​ 和 T 3 T_3 T3​ 的型号为 2SC1815&#xff0c; T 4 T_4 T4​ 的型号为 2SD525&#xff0c; T 2…

一款超好用的开源内存剖析器,今天教你怎么用!

Memray是一个由彭博社开发的、开源内存剖析器&#xff1b;开源一个多月&#xff0c;已经收获了超8.4k的star&#xff0c;是名副其实的明星项目。今天我们就给大家来推荐这款python内存分析神器。 Memray可以跟踪python代码、本机扩展模块和python解释器本身中内存分配&#xff…

【C++】运算符重载 ⑫ ( 等于判断 == 运算符重载 | 不等于判断 != 运算符重载 | 完整代码示例 )

文章目录 一、数组类 等号 运算符重载1、等于判断 运算符重载2、不等于判断 ! 运算符重载 二、完整代码示例1、Array.h 数组头文件2、Array.cpp 数组实现类3、Test.cpp 测试类4、执行结果 一、数组类 等号 运算符重载 1、等于判断 运算符重载 使用 成员函数 实现 等于判断 …

盒子模型的基础

盒子模型 边框&#xff08;border&#xff09; border可以设置元素的边框&#xff0c;边框分成三部分&#xff0c;边框的&#xff08;粗细&#xff09;边框的样式&#xff0c;边框的颜色 <style>div {width: 100px;height: 100px;border-width: 200;border-style: 边框…

【运行时数据区和程序计数器】

文章目录 1. 运行时数据区2. 程序计数器(PC 寄存器) 1. 运行时数据区 当我们通过前面的&#xff1a;类的加载-> 验证 -> 准备 -> 解析 -> 初始化 这几个阶段完成后&#xff0c;就会用到执行引擎对我们的类进行使用&#xff0c;同时执行引擎将会使用到我们运行时数据…

你了解的SpringCloud核心组件有哪些?他们各有什么作用?

SpringCloud 1.什么是 Spring cloud Spring Cloud 为最常见的分布式系统模式提供了一种简单且易于接受的编程模型&#xff0c;帮助开发人员构建有弹性的、可靠的、协调的应用程序。Spring Cloud 构建于 Spring Boot 之上&#xff0c;使得开发者很容易入手并快速应用于生产中。…

px4仿真实现无人机自主飞行

一,确定消息类型 无人机通过即在电脑是现自主飞行:思路如下。 通过Mavros功能包,将ROS消息转换为Mavlink消息。实现对无人机的控制。 几种消息之间的关系如下: 对于ROS数据,就是我们机载电脑执行ROS系统的数据。 对于Mavros消息,就是Mavros功能包内部的消息。查询网站…

【SkyWalking】SkyWalking是如何实现跨进程传播链路数据?

文章目录 一、简介1 为什么写这篇文章2 跨进程传播协议-简介 二、协议1 Standard Header项2 Extension Header项3 Correlation Header项 三、跨进程传播协议的源码分析1 OpenTracing规范2 通过dubbo插件分析跨进程数据传播3 分析跨进程传播协议的核心源码 四、小结参考 一、简介…

ERDAS 2022 安装教程

注意&#xff1a; 演示ERDAS版本为&#xff1a;2022.v16.7.0.1216 安装程序&#xff1a; 1、主程序&#xff1a;点击下载 2、许可文件&#xff1a;点击下载 3、IDM下载器&#xff1a;点击下载 下载速度&#xff1a; 浏览器下载速度慢&#xff0c;可以使用以上提供的IDM下…

[GWCTF 2019]我有一个数据库 phpMyAdmin 4.8.1后台文件包含漏洞

一开始打开是乱码 之前题目做过修复乱码的&#xff0c;得到这个 用dirsearch扫一下 一开始我是看到robots.txt 访问一下 访问一下phpinfo 也没啥&#xff0c;看到phpmyadimin 访问一下 没啥思路&#xff0c;看了wp 看到phpMyAdmin 4.8.1后台文件包含漏洞&#xff08;CV…

LabVIEW中不同颜色连线的含义

LabVIEW中不同颜色连线的含义 LabVIEW中的连线具有不同的颜色&#xff0c;样式和宽度。每个都代表了什么&#xff1f; 下表列出了常见的连线类型&#xff1a; 相关信息 请注意&#xff0c;类的连线颜色是可更改的。该表显示其默认外观。 连线用于在程序框图各对象间传递数据…

016 Spring Boot + Vue 图书管理系统

Spring Boot Vue 图书馆管理系统&#xff08;library-system&#xff09; 本地快捷预览项目 第一步&#xff1a;运行 db 文件夹下的springboot-vue.sql(询问作者获取)&#xff0c;创建springboot-vue数据库 第二步&#xff1a;修改后端数据库配置文件&#xff0c;启动后端 …

二次封装View Design的table组件,实现宽度自适应,内容在一行展示

由于table组件本身并不支持宽度自适应&#xff0c;但实际项目需要&#xff0c;而且多处有用到table组件&#xff0c;所以尝试着自己来二次封装一下组件 想法 刚开始的想法很简单&#xff0c;就是获取每一列中数据和标题在表格中的长度&#xff0c;然后将当中最大的长度作为该列…

Nginx配置文件的通用语法介绍

要是参考《Ubuntu 20.04使用源码安装nginx 1.14.0》安装nginx的话&#xff0c;nginx配置文件在/nginx/conf目录里边&#xff0c;/nginx/conf里边的配置文件结构如下图所示&#xff1a; nginx.conf是主配置文件&#xff0c;它是一个ascii文本文件。配置文件由指令&#xff08;…

分析“由于找不到vcruntime140.dll无法继续执行代码”这个问题的5个解决方法

当使用电脑时&#xff0c;我们难免会遇到各种问题。其中&#xff0c;“由于找不到vcruntime140.dll无法继续执行代码”是一个常见的错误&#xff0c;通常出现在运行使用C编写的应用程序时。这个问题可能会导致软件程序或游戏无法打开或运行。然而&#xff0c;只要我们掌握正确的…

大话机器学习准确率(Accuracy)、精确率(Pecision)、召回率(Recall)以及TP、FP、TN、FN

话说三国时期&#xff0c;乱世出人才&#xff0c;当时刘备让张飞帮忙招兵买马&#xff0c;寻找人才。张飞发公告以后&#xff0c;有10人来面试&#xff0c;这10人分为两类&#xff0c;人才和庸才&#xff0c;各占百分之五十&#xff0c;张飞的主要作用就是从这10人中识别出人才…

放大招,百度文心大模型4.0正在加紧训练,即将发布

插播一条快讯&#xff01; &#xfeff;&#xfeff;刚刚看到一篇报道&#xff0c;说百度正在加紧训练文心大模型4.0&#xff01;百度5月发布了文心大模型3.5&#xff0c;才4个多月又要发布4.0了&#xff0c;这迭代速度简直了。据说这次发布将在10月17日百度世界大会上进行&am…

strcat函数详解:字符串追加的利器

目录 一&#xff0c;strcat函数的简介 二&#xff0c;strcat函数的使用 三&#xff0c;strcat函数的注意事项 四&#xff0c;strcat函数的模拟实现 一&#xff0c;strcat函数的简介 strcat函数用于将源字符串追加到目标字符串的末尾&#xff0c;并返回一个指向目标字符串的…

QString、QLatin1String、QStringLiteral区别和用法以及效率

QString类 QString是Qt框架中提供的字符串类&#xff0c;用于处理Unicode字符串。它提供了许多方便的方法和功能&#xff0c;可以进行字符串的连接、查找、替换、截取等操作。QString类的对象是可变的&#xff0c;可以在运行时修改字符串内容。 . 由以上引出一个知识点&#xf…