合并多棵二叉搜索树

引入

给你 n 个 二叉搜索树的根节点 ,存储在数组 trees 中(下标从 0 开始),对应 n 棵不同的二叉搜索树。trees 中的每棵二叉搜索树 最多有 3 个节点 ,且不存在值相同的两个根节点。在一步操作中,将会完成下述步骤:

  • 选择两个 不同的 下标 i 和 j ,要求满足在 trees[i] 中的某个 叶节点 的值等于 trees[j] 的 根节点的值 。
  • 用 trees[j] 替换 trees[i] 中的那个叶节点。
  • 从 trees 中移除 trees[j] 。

如果在执行 n - 1 次操作后,能形成一棵有效的二叉搜索树,则返回结果二叉树的 根节点 ;如果无法构造一棵有效的二叉搜索树,返回 null 。
二叉搜索树是一种二叉树,且树中每个节点均满足下述属性:

  • 任意节点的左子树中的值都 严格小于 此节点的值。
  • 任意节点的右子树中的值都 严格大于 此节点的值。

叶节点是不含子节点的节点。

示例1(成功的情况)

请添加图片描述

输入:trees = [[2,1],[3,2,5],[5,4]]
输出:[3,2,5,1,null,4]
解释:
第一步操作中,选出 i=1 和 j=0 ,并将 trees[0] 合并到 trees[1] 中。
删除 trees[0] ,trees = [[3,2,5,1],[5,4]] 。
请添加图片描述
在第二步操作中,选出 i=0 和 j=1 ,将 trees[1] 合并到 trees[0] 中。
删除 trees[1] ,trees = [[3,2,5,1,null,4]] 。
请添加图片描述
结果树如上图所示,为一棵有效的二叉搜索树,所以返回该树的根节点。

示例2(不能的情况)

请添加图片描述

输入:trees = [[5,3,8],[3,2,6]]
输出:[]
解释:
选出 i=0 和 j=1 ,然后将 trees[1] 合并到 trees[0] 中。
删除 trees[1] ,trees = [[5,3,8,2,6]] 。
请添加图片描述
结果树如上图所示。仅能执行一次有效的操作,但结果树不是一棵有效的二叉搜索树,所以返回 null 。

示例3

请添加图片描述

输入:trees = [[5,4],[3]]
输出:[]
解释:无法执行任何操作。

提示

  • n == trees.length
  • 1 <= n <= 5 * 104
  • 每棵树中节点数目在范围 [1, 3] 内。
  • 输入数据的每个节点可能有子节点但不存在子节点的子节点
  • trees 中不存在两棵树根节点值相同的情况。
  • 输入中的所有树都是 有效的二叉树搜索树 。
  • 1 <= TreeNode.val <= 5 * 104

对于给定的第 i i i 棵树 tree [ i ] \textit{tree}[i] tree[i],记它根节点的值为 root [ i ] \textit{root}[i] root[i]。我们需要找到另一棵树,记为第 j   ( j ≠ i ) j~(j \neq i) j (j=i) 棵。第 jj 棵树必须存在一个值为 root [ i ] \textit{root}[i] root[i] 的叶节点,这样我们就可以把第 i i i 棵树与第 j j j 棵树合并。

如果不存在满足要求的第 j j j 棵树,那么第 i i i 棵树的根节点就必须是合并完成后的树的根节点。显然,这样的第 i i i 棵树必须恰好有且仅有一棵。

如果存在唯一的第 j j j 棵树,那么我们必须要将第 i i i 棵树和第 j j j 棵树合并。如果不这样做,合并完成后的树中就至少有两个值为 root [ i ] \textit{root}[i] root[i] 的节点,它就一定不是二叉搜索树了。

如果存在多棵可行的第 j j j 棵树,那么我们应当选择哪一棵呢?我们发现,由于题目保证不存在值相同的两个根节点,那么我们没有选择的那些树中,它们值为 root [ i ] \textit{root}[i] root[i] 的叶节点都会被保留,而我们选择的第 j j j 棵树与第 i i i 棵树合并后,也会留下一个值为 root [ i ] \textit{root}[i] root[i] 的节点。这样合并完成后的树中同样至少有两个值为 root [ i ] \textit{root}[i] root[i] 的节点,也一定不是二叉搜索树了。

因此,如果某一棵树的根节点如果需要合并,那么合并的方案是唯一的。

解法

  • 我们首先将每一棵树叶节点的值使用哈希集合 leaves \textit{leaves} leaves 存储下来,随后就可以找出合并完成后的树的根节点。记包含根节点的那棵树为 tree [ pivot ] \textit{tree}[\textit{pivot}] tree[pivot],则 root [ pivot ] \textit{root}[\textit{pivot}] root[pivot] 不能在 leaves \textit{leaves} leaves 中出现过。(题目说了不存在值相同的根节点,所以用hash会更快)
    如果不存在满足要求的 tree [ pivot ] \textit{tree}[\textit{pivot}] tree[pivot],那么就无法构造出一棵二叉搜索树;如果满足要求的 tree [ pivot ] \textit{tree}[\textit{pivot}] tree[pivot] 不唯一,那么我们随便挑选一棵即可(如果满足要求的树不唯一其实就无法构造出一个二叉搜索树了,这个唯一性在提示中我提到了)。

  • 我们知道,一棵二叉树是二叉搜索树,当且仅当它的中序遍历的序列是严格单调递增的。因此,我们从 tree [ pivot ] \textit{tree}[\textit{pivot}] tree[pivot] 的根节点开始,使用递归的方法对其进行一种特殊的中序遍历:

    • 当我们遍历到一个非叶节点时,我们按照常规的中序遍历的方法,继续进行遍历;
    • 当我们遍历到一个叶节点时,该叶节点可能会与另一棵树的根节点进行合并。设该叶节点的值为 xx,如果存在根节点值同样为 xx 的树,我们就将其与该叶节点进行合并。在合并完成之后,该叶节点可能会变为非叶节点,我们需要继续按照常规的中序遍历的方法,继续进行遍历。
  • 在遍历的过程中,如果我们发现遍历到的值不是严格单调递增的,说明无法构造出一棵二叉搜索树。同时,如果遍历结束,但存在某一棵树的根节点没有被遍历到,那也说明无法构造出一棵二叉搜索树。

代码:

/**
 * 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* canMerge(vector<TreeNode*>& trees) {
        // 存储所有叶节点值的哈希集合
        unordered_set<int> leaves;
        // 存储 (根节点值, 树) 键值对的哈希映射
        unordered_map<int, TreeNode*> candidates;
        for (TreeNode* tree: trees) {
            if (tree->left) {
                leaves.insert(tree->left->val);
            }
            if (tree->right) {
                leaves.insert(tree->right->val);
            }
            candidates[tree->val] = tree;
        }

        // 存储中序遍历上一个遍历到的值,便于检查严格单调性
        int prev = 0;
        
        // 中序遍历,返回值表示是否有严格单调性
        function<bool(TreeNode*)> dfs = [&](TreeNode* tree) {
            if (!tree) {
                return true;
            }

            // 如果遍历到叶节点,并且存在可以合并的树,那么就进行合并
            if (!tree->left && !tree->right && candidates.count(tree->val)) {
                tree->left = candidates[tree->val]->left;
                tree->right = candidates[tree->val]->right;
                // 合并完成后,将树从哈希映射中移除,以便于在遍历结束后判断是否所有树都被遍历过
                candidates.erase(tree->val);
            }
            
            // 先遍历左子树
            if (!dfs(tree->left)) {
                return false;
            }
            // 再遍历当前节点
            if (tree->val <= prev) {
                return false;
            };
            prev = tree->val;
            // 最后遍历右子树
            return dfs(tree->right);
        };
        
        for (TreeNode* tree: trees) {
            // 寻找合并完成后的树的根节点
            if (!leaves.count(tree->val)) {
                // 将其从哈希映射中移除
                candidates.erase(tree->val);
                // 从根节点开始进行遍历
                // 如果中序遍历有严格单调性,并且所有树的根节点都被遍历到,说明可以构造二叉搜索树
                return (dfs(tree) && candidates.empty()) ? tree : nullptr;
            }
        }
        return nullptr;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AlbertOS

还会有大爷会打钱?

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

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

打赏作者

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

抵扣说明:

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

余额充值