leecode刷题笔记——数据结构(二)

tree 树

144. Binary Tree Preorder Traversal

Given the root of a binary tree, return the preorder traversal of its nodes’ values.

在这里插入图片描述

Example 1:
Input: root = [1,null,2,3]
Output: [1,2,3]

Example 2:
Input: root = []
Output: []

Example 3:
Input: root = [1]
Output: [1]

Constraints:
The number of nodes in the tree is in the range [0, 100].
-100 <= Node.val <= 100

Follow up: Recursive solution is trivial, could you do it iteratively?

我的思路:递归
二叉树的前序遍历:按照访问根节点——左子树——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候,我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。

/**
 * 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> preorderTraversal(TreeNode* root) {
        if (root)
        {
            this->ans.push_back(root->val);
            preorderTraversal(root->left);
            preorderTraversal(root->right);
        }
        return this->ans;
    }
private:
    vector<int> ans;
};

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
  • 空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O ( log ⁡ n ) O(\log n) O(logn),最坏情况下树呈现链状,为 O(n)。

方法二:迭代
思路与算法

我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,区别在于递归的时候隐式地维护了一个栈 (用于存寻找左子树时,遍历的TreeNode),而我们在迭代的时候需要显式地将这个栈模拟出来,其余的实现与细节都相同,具体可以参考下面的代码。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> stk;
        TreeNode* node = root;
        while(!stk.empty() || node)
        {
            while(node)
            {
                this->ans.emplace_back(node->val);//👀
                stk.emplace(node);
                node = node->left;
            }
            node = stk.top(); // 当没有左子树后,返回前一个treenode
            stk.pop();
            node = node->right;// 从新的右子树开始往下重新添加左子树,直到右子树为空时,再往上跳一个TreeNode
        }

        return this->ans;
    }
private:
    vector<int> ans;
};

方法三:Morris 遍历(待理解)

有一种巧妙的方法可以在线性时间内,只占用常数空间来实现前序遍历。这种方法由 J. H. Morris 在 1979 年的论文「Traversing Binary Trees Simply and Cheaply」中首次提出,因此被称为 Morris 遍历。

Morris 遍历的核心思想是利用树的大量空闲指针,实现空间开销的极限缩减。其前序遍历规则总结如下:

  1. 新建临时节点,令该节点为 root;

  2. 如果当前节点的左子节点为空,将当前节点加入答案,并遍历当前节点的右子节点;

  3. 如果当前节点的左子节点不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点:
    3.1 如果前驱节点的右子节点为空,将前驱节点的右子节点设置为当前节点。然后将当前节点加入答案,并将前驱节点的右子节点更新为当前节点。当前节点更新为当前节点的左子节点。
    3.2 如果前驱节点的右子节点为当前节点,将它的右子节点重新设为空。当前节点更新为当前节点的右子节点。

  4. 重复步骤 2 和步骤 3,直到遍历结束。

这样我们利用 Morris 遍历的方法,前序遍历该二叉树,即可实现线性时间与常数空间的遍历。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode *root) {
        vector<int> res;
        if (root == nullptr) {
            return res;
        }

        TreeNode *p1 = root, *p2 = nullptr;

        while (p1 != nullptr) {
            p2 = p1->left;
            if (p2 != nullptr) {
                while (p2->right != nullptr && p2->right != p1) {
                    p2 = p2->right;
                }
                if (p2->right == nullptr) {
                    res.emplace_back(p1->val);
                    p2->right = p1;
                    p1 = p1->left;
                    continue;
                } else {
                    p2->right = nullptr;
                }
            } else {
                res.emplace_back(p1->val);
            }
            p1 = p1->right;
        }
        return res;
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n 是二叉树的节点数。没有左子树的节点只被访问一次,有左子树的节点被访问两次。
  • 空间复杂度:O(1)。只操作已经存在的指针(树的空闲指针),因此只需要常数的额外空间

94. Binary Tree Inorder Traversal 中序遍历

Given the root of a binary tree, return the inorder traversal of its nodes’ values.

Example 1:
Input: root = [1,null,2,3]
Output: [1,3,2]

Example 2:
Input: root = []
Output: []

Example 3:
Input: root = [1]
Output: [1]
 
Constraints:
The number of nodes in the tree is in the range [0, 100].
-100 <= Node.val <= 100

Follow up: Recursive solution is trivial, could you do it iteratively?

我的思路:无法熟练使用栈复现方法
官方思路一:递归
二叉树的中序遍历:按照访问左子树——根节点——右子树的方式遍历这棵树,而在访问左子树或者右子树的时候我们按照同样的方式遍历,直到遍历完整棵树。因此整个遍历过程天然具有递归的性质,我们可以直接用递归函数来模拟这一过程。

定义 i n o r d e r ( r o o t ) inorder(root) inorder(root) 表示当前遍历到 root \textit{root} root 节点的答案,那么按照定义,我们只要递归调用 i n o r d e r ( r o o t . l e f t ) inorder(root.left) inorder(root.left) 来遍历 root \textit{root} root 节点的左子树,然后将 root \textit{root} root 节点的值加入答案,再递归调用 i n o r d e r ( r o o t . r i g h t ) inorder(root.right) inorder(root.right) 来遍历 root \textit{root} root 节点的右子树即可,递归终止的条件为碰到空节点。

class Solution {
public:
    void inorder(TreeNode* root, vector<int>& node)
    {
        if (!root) return;
        inorder(root->left, node);
        node.push_back(root->val);
        inorder(root->right, node);
    }

    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> ans;
        inorder(root, ans);
        return ans;
    }
};

方法二:迭代

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;
    }
};

145. Binary Tree Postorder Traversal

Given the root of a binary tree, return the postorder traversal of its nodes’ values.

在这里插入图片描述

Example 1:
Input: root = [1,null,2,3]
Output: [3,2,1]

Example 2:
Input: root = []
Output: []

Example 3:
Input: root = [1]
Output: [1]
 

Constraints:
The number of the nodes in the tree is in the range [0, 100].
-100 <= Node.val <= 100

Follow up: Recursive solution is trivial, could you do it iteratively?

方法一:递归

class Solution {
public:
    void postorder(TreeNode*node, vector<int>& ans) //! ans需要用参数引用传入
    {   
        if (!node) return ;
        postorder(node->left, ans);
        postorder(node->right, ans);
        ans.emplace_back(node->val);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        postorder(root, ans);
        return ans;
    }
};

方法二:迭代

class Solution {
public:
    vector<int> postorderTraversal(TreeNode *root) {
        if (!root) return {};
        vector<int> ans;
        stack<TreeNode *> stk;
        TreeNode *prev = nullptr;
        auto node = root;
        while (!stk.empty() || node) {
            // 1.遍历到最左子节点
            while (node) {
                stk.emplace(node);
                node = node->left;
            }
            node = stk.top(); stk.pop();
            // 2.遍历最左子节点的右子树(右子树存在 && 未访问过)
            if (node->right && node->right != prev) {
                // 重复压栈以记录当前路径分叉节点
                stk.emplace(node);
                node = node->right;      
            } else {
                // 后序:填充vec在node->left和node->right后面
                // 注意:此时node的左右子树应均已完成访问
                ans.emplace_back(node->val); //👀
                // 避免重复访问右子树[记录当前节点便于下一步对比]
                prev = node;
                // 避免重复访问左子树[设空节点]
                node = nullptr;
            }
        }
        return ans;
    }
};

树的三种遍历(前序遍历,中序遍历,后序遍历)总结

  • 迭代法
    套路:声明一个void函数inorder(TreeNode* root, vector<int>& ans)
  1. 前序遍历:打印 - 左 - 右
  2. 中序遍历:左 - 打印 - 右
  3. 后序遍历:左 - 右 - 打印 :
    postorder(root->left, ans);
    postorder(root->right, ans);
    ans.push_back(root->val);
  4. 截止条件:当前节点root为空。

举例中序遍历代码实现

class Solution {
public:
    void inorder(TreeNode* root, vector<int>& ans)
    {
        if (!root) return;
        inorder(root->left, ans);
        ans.push_back(root->val);
        inorder(root->right, ans);
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ans;
        inorder(root, ans);
        return ans;
    }
};

102. Binary Tree Level Order Traversal 层序遍历 (BFS广度优先算法的案例)

Given the root of a binary tree, return the level order traversal of its nodes’ values. (i.e., from left to right, level by level).
在这里插入图片描述

Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]

Example 2:
Input: root = [1]
Output: [[1]]

Example 3:
Input: root = []
Output: []

Constraints:
The number of nodes in the tree is in the range [0, 2000].
-1000 <= Node.val <= 1000

思路:广度优先搜索
我们可以想到最朴素的方法是用一个二元组 (node, level) 来表示状态,它表示某个节点和它所在的层数,每个新进队列的节点的 level 值都是父亲节点的 level 值加一。最后根据每个点的 level 对点进行分类,分类的时候我们可以利用哈希表,维护一个以 level 为键,对应节点值组成的数组为值,广度优先搜索结束以后按键 level 从小到大取出所有值,组成答案返回即可。

考虑如何优化空间开销:如何不用哈希映射,并且只用一个变量 node 表示状态,实现这个功能呢?
我们可以用一种巧妙的方法修改广度优先搜索:

  • 首先根元素入队
  • 当队列不为空的时候
    1 . 求当前队列的长度 s i s_i si
    2 . 依次从队列中取 s i s_i si个元素进行拓展,然后进入下一次迭代

它和普通广度优先搜索的区别在于,普通广度优先搜索每次只取一个元素拓展,而这里每次取 s i s_i si个元素。在上述过程中的第 i i i 次迭代就得到了二叉树的第 i 层的 s i s_i si个元素。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ans;
        if (!root) return ans;
        TreeNode* node = root;
        queue<TreeNode*> q;
        q.push(node);

        while (!q.empty())
        {
            ans.push_back(vector<int> ());
            int num = q.size();
            for (auto i=0; i<num; i++)
            {
                node = q.front(); q.pop();
                ans.back().push_back(node->val);
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        return ans;
    }
};

复杂度分析

记树上所有节点的个数为 n n n

  • 时间复杂度:每个点进队出队各一次,故渐进时间复杂度为 O(n)。
  • 空间复杂度:队列中元素的个数不超过 n 个,故渐进空间复杂度为 O(n)。

104. Maximum Depth of Binary Tree

Given the root of a binary tree, return its maximum depth.

A binary tree’s maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
在这里插入图片描述

Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 3

Example 2:
Input: root = [1,null,2]
Output: 2
 

Constraints:

The number of nodes in the tree is in the range [0, 104].
-100 <= Node.val <= 100

思路一:深度优先搜索DFS
如果我们知道了左子树和右子树的最大深度 l 和 r,那么该二叉树的最大深度即为
max ⁡ ( l , r ) + 1 \max(l,r) + 1 max(l,r)+1
而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。具体而言,在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O(1) 时间内计算出当前二叉树的最大深度。递归在访问到空节点时退出。

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (!root) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。
  • 空间复杂度: O ( height ) O(\textit{height}) O(height),其中 height \textit{height} height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。

方法二:广度优先搜索

我们也可以用「广度优先搜索」的方法来解决这道题目,但我们需要对其进行一些修改,此时我们广度优先搜索的队列里存放的是「当前层的所有节点」。每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展,这样能保证每次拓展完的时候队列里存放的是当前层的所有节点,即我们是一层一层地进行拓展,最后我们用一个变量 ans \textit{ans} ans 来维护拓展的次数,该二叉树的最大深度即为 ans \textit{ans} ans

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (!root) return 0;
        queue<TreeNode*> q;
        TreeNode* node = root;
        q.push(node);
        int ans = 0; // 👀
        while (!q.empty())
        {
            int num = q.size();
            for (int i=0; i<num; i++)
            {
                node = q.front(); q.pop();
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
            ans ++; // 👀
        }
        return ans;
    }
};

comment:基本与层序遍历算法一致,就添加了一个新的int变量ans来统计层数
复杂度分析

  • 时间复杂度:O(n),其中 n 为二叉树的节点个数。与方法一同样的分析,每个节点只会被访问一次。
  • 空间复杂度:此方法空间的消耗取决于队列存储的元素数量,其在最坏情况下会达到 O(n)。

101. Symmetric Tree

Given the root of a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center).

Example 1:
在这里插入图片描述


Input: root = [1,2,2,3,4,4,3]
Output: true

Example 2:
在这里插入图片描述

Input: root = [1,2,2,null,3,null,3]
Output: false
 

Constraints:

The number of nodes in the tree is in the range [1, 1000].
-100 <= Node.val <= 100

Follow up: Could you solve it both recursively and iteratively?

我的思路:BFS❌

class Solution {
public:
    // bool is_equal(vector<int>* ans) // 与下述方法等价,挨个判断对称
    // {
    //     int num = ans->size();
    //     for (int i=0; i<num; i++)
    //     {
    //         if ((*ans)[i]!=(*ans)[num-i-1]) return false;
    //     }
    //     return true;
    // }
    bool is_equal(vector<int>* ans) // 判断向量是否对称
    {
        vector<int> ans_old = *ans;
        reverse(ans->begin(), ans->end());
        return *ans==ans_old? true : false;
    }

    bool isSymmetric(TreeNode* root) {
        if (root == nullptr) return true;
        queue<TreeNode*> q;
        q.push(root);
        bool start = true;
        int count = 0;  
        while (!q.empty())
        {
            int num = q.size();
            if (num%2!=0 && start==false) return false;
            vector<int>* v = new vector<int>();
            
            for (int i=0; i<num; i++)
            {
                root = q.front(); q.pop();
                v->push_back(root->val);
                if (root->left) 
                {
                    q.push(root->left);
                    count++;
                }
                if (root->right) 
                {
                    q.push(root->right);
                    count--;
                }
            }
            if (count!=0) return false; // ❌,不可靠
            bool ans = is_equal(v);
            delete v;
            if (!ans) return false;
        }
        return true;
    }
};

错误案例: [2,3,3,4,5,5,4,null,null,8,9,null,null,9,8];
通过count来计数 :遍历左子树右子树的次数,不可靠。

方法一:递归

如果一个树的左子树与右子树镜像对称,那么这个树是对称的。

在这里插入图片描述

因此,该问题可以转化为:两个树在什么情况下互为镜像?

如果同时满足下面的条件,两个树互为镜像:

  • 它们的两个根结点具有相同的值
  • 每个树的右子树都与另一个树的左子树镜像对称
    在这里插入图片描述

我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树,p 指针和 q 指针一开始都指向这棵树的根,随后 p 右移时,q 左移,p 左移时 q 右移。每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。

代码如下。

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);
    }
};

复杂度分析

假设树上一共 n 个节点。

  • 时间复杂度:这里遍历了这棵树,渐进时间复杂度为 O(n)。
  • 空间复杂度:这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n,故渐进空间复杂度为 O(n)。

方法二:迭代
思路和算法

「方法一」中我们用递归的方法实现了对称性的判断,那么如何用迭代的方法实现呢?首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。

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);
    }
};

226. Invert Binary Tree

Given the root of a binary tree, invert the tree, and return its root.

Example 1:
在这里插入图片描述

Input: root = [4,2,7,1,3,6,9]
Output: [4,7,2,9,6,3,1]

Example 2:
Input: root = [2,1,3]
Output: [2,3,1]

Example 3:
Input: root = []
Output: []
 
Constraints:
The number of nodes in the tree is in the range [0, 100].
-100 <= Node.val <= 100

我的思路:递归
如果左子树存在,则翻转该子树
如果右子树存在,则翻转该子树

class Solution {
public:
    void invert(TreeNode* root)
    {
        TreeNode* tmp = root->left;
        root->left = root->right;
        root->right = tmp;

        if (root->left) invert(root->left);
        if (root->right) invert(root->right);
        return;
    }

    TreeNode* invertTree(TreeNode* root) {
        if (!root) return root;
        invert(root);
        return root;
    }
};

方法二:DFS 用一个stack来代替递归过程
方法三:BFS 用queue

112. Path Sum 路径总和

Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that adding up all the values along the path equals targetSum.

A leaf is a node with no children.

Example 1:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
Output: true
Explanation: The root-to-leaf path with the target sum is shown.

Example 2:
Input: root = [1,2,3], targetSum = 5
Output: false
Explanation: There two root-to-leaf paths in the tree:
(1 --> 2): The sum is 3.
(1 --> 3): The sum is 4.
There is no root-to-leaf path with sum = 5.

Example 3:
Input: root = [], targetSum = 0
Output: false
Explanation: Since the tree is empty, there are no root-to-leaf paths.
 

Constraints:
The number of nodes in the tree is in the range [0, 5000].
-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000

我的思路:DFS ❌

  1. 注意 此方法是根结点到叶子结点的距离,中间的和不算
  2. stack弹出node时,根结点附近计算不清楚
class Solution {
public:
    // bool dfs0(TreeNode* root, int target) // 此方法是从一开始就计算路径和,不满足注意事项1
    // {
    //     int count = root->val;
    //     if (target == count) return true;
    //     TreeNode* node = root;
    //     stack<TreeNode*> stk;
    //     stk.push(node);

    //     while (!stk.empty())
    //     {
    //         while (node->left)
    //         {
    //             node = node->left;
    //             count += node->val;
    //             if (target == count) return true;
    //             stk.push(node);
    //         }
    //         node = stk.top(); stk.pop();
    //         count -= node->val;
    //         if (node->right)
    //         {
    //             count += node->val;
    //             node = node->right;
    //             count += node->val;
    //             if (target == count) return true;
    //             stk.push(node);
    //         }
            
    //     }
    //     return false;
    // }
    bool dfs(TreeNode* root, int target)
    {
        int count = root->val;
        if (target == count && !root->left && !root->right) return true;
        TreeNode* node = root;
        stack<TreeNode*> stk;
        stk.push(node);

        while (!stk.empty())
        {
            while (node->left)
            {
                node = node->left;
                count += node->val;
                stk.push(node);
            }
            node = stk.top(); stk.pop();
            if (target == count && !node->right && !stk.empty()) return true;
            count -= node->val;
            if (node->right)
            {
                count += node->val;
                node = node->right;
                count += node->val;
                stk.push(node);
            }
        }
        return false;
    }

    bool hasPathSum(TreeNode* root, int targetSum) {
        if (!root) return false;
        return dfs(root, targetSum);
    }
};

错误案例:[-2,null,-3]

方法二:递归
观察要求我们完成的函数,我们可以归纳出它的功能:询问是否存在从当前节点 root 到叶子节点的路径,满足其路径和为 sum。

假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。

不难发现这满足递归的性质,

  1. 若当前节点就是叶子节点,那么我们直接判断 sum 是否等于 val 即可(因为路径和已经确定,就是当前节点的值,我们只需要判断该路径和是否满足条件)。
  2. 若当前节点不是叶子节点,我们只需要递归地询问它的子节点是否能满足条件即可。
class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if (root == nullptr) {
            return false;
        }
        if (root->left == nullptr && root->right == nullptr) {
            return sum == root->val;
        }
        return hasPathSum(root->left, sum - root->val) ||
               hasPathSum(root->right, sum - root->val);
    }
};

复杂度分析

  • 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。

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

方法三:BFS
首先我们可以想到使用广度优先搜索的方式,记录从根节点到当前节点的路径和,以防止重复计算。

这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。

class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if (root == nullptr) {
            return false;
        }
        queue<TreeNode *> que_node;
        queue<int> que_val;
        que_node.push(root);
        que_val.push(root->val);
        while (!que_node.empty()) {
            TreeNode *now = que_node.front();
            int temp = que_val.front();
            que_node.pop();
            que_val.pop();
            if (now->left == nullptr && now->right == nullptr) {
                if (temp == sum) {
                    return true;
                }
                continue;
            }
            if (now->left != nullptr) {
                que_node.push(now->left);
                que_val.push(now->left->val + temp);
            }
            if (now->right != nullptr) {
                que_node.push(now->right);
                que_val.push(now->right->val + temp);
            }
        }
        return false;
    }
};

复杂度分析

  • 时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。

  • 空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于队列的开销,队列中的元素个数不会超过树的节点数。

700. Search in a Binary Search Tree

You are given the root of a binary search tree (BST) and an integer val.

Find the node in the BST that the node’s value equals val and return the subtree rooted with that node. If such a node does not exist, return null.

Example 1:


Input: root = [4,2,7,1,3], val = 2
Output: [2,1,3]
Example 2:


Input: root = [4,2,7,1,3], val = 5
Output: []

Constraints:

The number of nodes in the tree is in the range [1, 5000].
1 <= Node.val <= 107
root is a binary search tree.
1 <= val <= 107

我的思路:dfs递归

class Solution {
public:
    TreeNode* dfs(TreeNode*root, int val)
    {
        TreeNode* ans = root;
        if (ans->val==val) return ans;
        if (root->left && ans->val!=val) ans = dfs(root->left, val);//2
        if (root->right && ans->val!=val) ans = dfs(root->right, val);//👀
        return ans;
    }
    TreeNode* searchBST(TreeNode* root, int val) {
        TreeNode* node = dfs(root, val);
        return node->val==val? node:nullptr;
    }
};

注意1:二叉搜索树是具有一定的性质的
注意2:ans是一个迭代的变量,故👀处不能把if中的判断改为if (ans->right && ans->val!=val) 因为,ans已经不是当前的ans了。

方法二:利用性质递归
二叉搜索树满足如下性质:

  • 左子树所有节点的元素值均小于根的元素值;
  • 右子树所有节点的元素值均大于根的元素值。
class Solution {
public:
    TreeNode *searchBST(TreeNode *root, int val) {
        if (root == nullptr) {
            return nullptr;
        }
        if (val == root->val) {
            return root;
        }
        return searchBST(val < root->val ? root->left : root->right, val);
    }
};

复杂度分析

时间复杂度:O(N),其中 N 是二叉搜索树的节点数。最坏情况下二叉搜索树是一条链,且要找的元素比链末尾的元素值还要小(大),这种情况下我们需要递归 N 次。

空间复杂度:O(N)。最坏情况下递归需要 O(N) 的栈空间。

方法三:迭代
不使用stack的迭代

class Solution {
public:
    TreeNode *searchBST(TreeNode *root, int val) {
        while (root) {
            if (val == root->val) {
                return root;
            }
            root = val < root->val ? root->left : root->right;
        }
        return nullptr;
    }
};

复杂度分析

时间复杂度:O(N),其中 N 是二叉搜索树的节点数。最坏情况下二叉搜索树是一条链,且要找的元素比链末尾的元素值还要小(大),这种情况下我们需要迭代 N 次。

空间复杂度:O(1)。没有使用额外的空间。

701. Insert into a Binary Search Tree

You are given the root node of a binary search tree (BST) and a value to insert into the tree. Return the root node of the BST after the insertion. It is guaranteed that the new value does not exist in the original BST.

Notice that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return any of them.

Example 1:

Input: root = [4,2,7,1,3], val = 5
Output: [4,2,7,1,3,5]
Explanation: Another accepted tree is:

Example 2:

Input: root = [40,20,60,10,30,50,70], val = 25
Output: [40,20,60,10,30,50,70,null,null,25]
Example 3:

Input: root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
Output: [4,2,7,1,3,5]

Constraints:

The number of nodes in the tree will be in the range [0, 104].
-108 <= Node.val <= 108
All the values Node.val are unique.
-108 <= val <= 108
It’s guaranteed that val does not exist in the original BST.

我的思路:迭代

class Solution {
public:

    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (root==nullptr) 
        {
            TreeNode* node = new TreeNode(val);
            return node;
        }
        TreeNode* node = root;
        while (node)
        {   TreeNode* tmp = new TreeNode();
            tmp = node;
            node = (node->val < val) ? node->right : node->left;
            if (node==nullptr) 
            {
                node = tmp;
                if (node->val < val)
                {
                    node->right = new TreeNode(val);
                }else node->left = new TreeNode(val);
                break;
            }
        }
        return root;
    }
};

复杂度分析

  • 时间复杂度:O(N),其中 NN 为树中节点的数目。最坏情况下,我们需要将值插入到树的最深的叶子结点上,而叶子节点最深为 O(N)。

  • 空间复杂度:O(1)。我们只使用了常数大小的空间。

方法二:递归

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (!root) return new TreeNode{val};
        if (val < root->val) 
            root->left = insertIntoBST(root->left, val);
        else 
            root->right = insertIntoBST(root->right, val);
        return root;
    }
};

98. Validate Binary Search Tree 有效的二叉搜索树

Given the root of a binary tree, determine if it is a valid binary search tree (BST).

A valid BST is defined as follows:

The left subtree of a node contains only nodes with keys less than the node’s key.
The right subtree of a node contains only nodes with keys greater than the node’s key.
Both the left and right subtrees must also be binary search trees.

Example 1:


Input: root = [2,1,3]
Output: true
Example 2:


Input: root = [5,1,4,null,null,3,6]
Output: false
Explanation: The root node's value is 5 but its right child's value is 4.

我的方法:dfs递归,中序遍历
将遍历得到的vector逐一比较元素大小,判断是否为升序即可。

class Solution {
public:
    void dfs(TreeNode* root, vector<int>& list)
    {
        if (root==nullptr) return;
        if (root->left) dfs(root->left, list);
        list.push_back(root->val);
        if (root->right) dfs(root->right, list);
        return;
    }
    bool isValidBST(TreeNode* root) {
        vector<int> list;
        dfs(root, list);
        vector<int>::iterator i=list.begin();
        while (i!=list.end())
        {
            if (i+1!=list.end())
            {
                if (*(i+1)<=*i) return false;
            }
            i++;
        }
        return true;
    }
};
  • 时间复杂度 : O(n)
  • 空间复杂度:O(n)

此方法可改进为迭代法,不用创建vector

方法二:中序遍历,迭代

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) 的空间。

方法三:递归
要解决这道题首先我们要了解二叉搜索树有什么性质可以给我们利用,由题目给出的信息我们可以知道:如果该二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;它的左右子树也为二叉搜索树。

这启示我们设计一个递归函数 h e l p e r ( r o o t , l o w e r , u p p e r ) helper(root, lower, upper) helper(root,lower,upper) 来递归判断,函数表示考虑以 root 为根的子树,判断子树中所有节点的值是否都在 ( l , r ) (l,r) (l,r) 的范围内(注意是开区间)。如果 root 节点的值 val 不在 ( l , r ) (l,r) (l,r) 的范围内说明不满足条件直接返回,否则我们要继续递归调用检查它的左右子树是否满足,如果都满足才说明这是一棵二叉搜索树。

那么根据二叉搜索树的性质,在递归调用左子树时,我们需要把上界 u p p e r upper upper 改为 r o o t . v a l root.val root.val,即调用 h e l p e r ( r o o t . l e f t , l o w e r , r o o t . v a l ) helper(root.left, lower, root.val) helper(root.left,lower,root.val),因为左子树里所有节点的值均小于它的根节点的值。同理递归调用右子树时,我们需要把下界 l o w e r lower lower 改为 r o o t . v a l root.val root.val,即调用 h e l p e r ( r o o t . r i g h t , r o o t . v a l , u p p e r ) helper(root.right, root.val, upper) helper(root.right,root.val,upper)

函数递归调用的入口为 h e l p e r ( r o o t , − i n f , + i n f ) helper(root, -inf, +inf) helper(root,inf,+inf) i n f inf inf 表示一个无穷大的值。

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);
        // LLONG_MIN:代表当前平台上最小的 long long 类型整数; 
        // LLONG_MAX:代表当前平台上最大的 long long 类型整数; 
        // ULLONG_MIN:代表当前平台上最大的 unsigned long long 类型整数(无符号超长整型的最小值为 0);
    }
};

复杂度分析

时间复杂度 : O(n),其中 nn 为二叉树的节点个数。在递归调用的时候二叉树的每个节点最多被访问一次,因此时间复杂度为 O(n)。

空间复杂度 : O(n),其中 n 为二叉树的节点个数。递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度,即二叉树的高度。最坏情况下二叉树为一条链,树的高度为 n ,递归最深达到 n 层,故最坏情况下空间复杂度为 O(n) 。

653. Two Sum IV - Input is a BST

Given the root of a Binary Search Tree and a target number k, return true if there exist two elements in the BST such that their sum is equal to the given target.

Example 1:
Input: root = [5,3,6,2,4,null,7], k = 9
Output: true

Example 2:
Input: root = [5,3,6,2,4,null,7], k = 28
Output: false

我的思路:dfs中序遍历
声明一个vector存结果,再遍历它得出答案

class Solution {
public:
    void inorder(TreeNode* root, vector<int>& v)
    {
        if (root==nullptr) return;
        inorder(root->left, v);
        v.push_back(root->val);
        inorder(root->right, v);
        return;
    }
    bool findTarget(TreeNode* root, int k) {
        vector<int> v;
        inorder(root, v);

        for (auto it=v.begin(); it!=v.end(); it++)
        {   
            if (it!=v.end())
            {
                if (find(it+1, v.end(), k-*it)!=v.end()) return true;
            }
        }
        return false;
    }
};

改进:

  1. 将vector换成unordered map
  2. 由于中序vector是个有序数组,故可用二分查找(target-k)、双指针法等

方法二:哈希表

最简单的方法就是遍历整棵树,找出所有可能的组合,判断是否存在和为 k k k 的一对节点。现在在此基础上做一些改进。

如果存在两个元素之和为 k k k,即 x + y = k x+y=k x+y=k,并且已知 x x x 是树上一个节点的值,则只需判断树上是否存在一个值为 y y y的节点,使得 y = k − x y=k-x y=kx。基于这种思想,在树的每个节点上遍历它的两棵子树(左子树和右子树),寻找另外一个匹配的数。在遍历过程中,将每个节点的值都放到一个 s e t set set 中。

对于每个值为 p p p 的节点,在 s e t set set 中检查是否存在 k − p k-p kp。如果存在,那么可以在该树上找到两个节点的和为 k k k;否则,将 p p p 放入到 s e t set set 中。

如果遍历完整棵树都没有找到一对节点和为 k k k,那么该树上不存在两个和为 k k k 的节点。

class Solution {
public:
    bool find(TreeNode* root, int k, set<int>& set) { //👀
        if (root == nullptr)
            return false;
        if (set.find(k - root->val)!=set.end())
            return true;
        set.insert(root->val);
        return find(root->left, k, set) || find(root->right, k, set);
    }

    bool findTarget(TreeNode* root, int k) {
        set<int> set;
        return find(root, k, set);
    }
};

注意👀:set<int>& set,递归函数参数需加引用,否则值传不进去。

复杂度分析

  • 时间复杂度:O(n),其中 N 是节点的数量。最坏的情况下,整棵树被遍历一次。
  • 空间复杂度:O(n)。最坏的情况下,set 存储 n 个节点的值。

方法三:BST性质【双指针】

在本方法中利用 BST 的性质,BST 的中序遍历结果是按升序排列的。因此,中序遍历给定的 BST,并将遍历结果存储到 l i s t list list中。

遍历完成后,使用两个指针 l l l r r r 作为 l i s t list list 的头部索引和尾部索引。然后执行以下操作:

检查 l l l r r r 索引处两元素之和是否等于 k k k。如果是,立即返回 T r u e True True

如果当前两元素之和小于 k k k,则更新 l l l 指向下一个元素。这是因为当我们需要增大两数之和时,应该增大较小数。

如果当前两元素之和大于 k k k,则更新 r r r 指向上一个元素。这是因为当我们需要减小两数之和时,应该减小较大数。

重复步骤一至三,直到左指针 l l l 大于右指针 r r r

如果左指针 l l l 到右指针 r r r 的右边,则返回 F a l s e False False

注意,在任何情况下,都不应该增大较大的数,也不应该减小较小的数。这是因为如果当前两数之和大于 k k k,不应该首先增大 l i s t [ r ] list[r] list[r] 的值。类似的,也不应该首先减小 l i s t [ l ] list[l] list[l] 的值。

class Solution {
public:
    bool findTarget(TreeNode* root, int k) {
        vector<int> res;
        inorder(root,res);
        int left = 0;
        int right = res.size()-1;
        while(left<right)
        {
            int sum = res[left] + res[right];
            if(sum == k) return true;
            else if(sum < k) //和比目标值小,则移动左指针 
            {
                left++;
            }
            else 
            {
                right--; //和比目标值大,则移动右指针 
            }
        }
        return false;
        
    }
    void inorder(TreeNode *root,vector<int> &res)
    {
        if(root)
        {
            inorder(root->left,res);
            res.push_back(root->val);
            inorder(root->right,res);
        }
    }
};

复杂度分析

时间复杂度: O ( n ) O(n) O(n),其中 n n n 是树中节点的数量。本方法需要中序遍历整棵树。
空间复杂度: O ( n ) O(n) O(n) l i s t list list 中存储 n n n 个元素。

235. Lowest Common Ancestor of a Binary Search Tree 二叉树的最近公共祖先

Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BST.

According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”

Example 1:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
Output: 6
Explanation: The LCA of nodes 2 and 8 is 6.

Example 2:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
Output: 2
Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.

Example 3:
Input: root = [2,1], p = 2, q = 1
Output: 2
 

Constraints:

The number of nodes in the tree is in the range [2, 105].
-109 <= Node.val <= 109
All Node.val are unique.
p != q
p and q will exist in the BST.

我的思路:dfs迭代
声明TreeNode* max, min; 分别对应p,q的最大最小值。
当遍历到结点满足 node->val<=max->val && node->val>=min->val时,返回当前node即可

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* node = root;
        stack<TreeNode*> stk;
        stk.push(node);

        TreeNode* max;
        TreeNode* min;
        if (p->val > q->val)
        {
            max = p;
            min = q;
        }
        else { max = q; min = p; }

        while(!stk.empty() || node!=nullptr)
        {
            while(node)
            {   
                if (node->val<=max->val && node->val>=min->val) return node;
                stk.push(node);
                node = node->left;
            }
            node = stk.top(); stk.pop();
            node = node->right;
        }
        return nullptr;
    }
};

复杂度: O(n), O(n)

方法二:递归

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
        if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        return root;
    }
}

方法三:迭代,不使用栈
由于本题只需要返回最近公共祖先,故不需要stack去存node迭代结点

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ancestor = root;
        while (true) {
            if (p->val < ancestor->val && q->val < ancestor->val) {
                ancestor = ancestor->left;
            }
            else if (p->val > ancestor->val && q->val > ancestor->val) {
                ancestor = ancestor->right;
            }
            else {
                break;
            }
        }
        return ancestor;
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n 是给定的二叉搜索树中的节点个数。分析思路与方法一相同。
  • 空间复杂度:O(1)。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值