【深度优先】LeetCode1932:合并多棵二叉搜索树

作者推荐

动态规划LeetCode2552:优化了6版的1324模式

题目

给你 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.

分析

正确分析

能合并则合并,如果合并树的数量为1,则正确。
不需要考虑:叶子相同,那个叶子合并根的问题,两个叶子相同,那个叶子合并最后都不会是合法的搜索树。

错误分析

vMin 记录各子树的最小值,vMax记录各子树的最大值。 成为左子树的条件:一,叶子的值等子树根的值。 二,子树的最大值小于父树的值。成为右子树的条件:一,叶子的值等子树根的值。 二,子树的最小值大于父树的值。

错误原因

父子符合条件,祖孙不符合条件。

解决方法

判断是否是合法的树。

变量解释

vMin[i]以trees[i]为根的树的最小节点值
vMax[i]以trees[i]为根的树的最大节点值
mValueIndexkey:trees[i]根节点的值;value:i。

代码

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) {
m_c = trees.size();
vector vMin(m_c), vMax(m_c);
unordered_map<int, int> mValueIndex;
for (int i = 0; i < m_c; i++)
{
const auto& p = trees[i];
mValueIndex[p->val] = i;
vMin[i] = (nullptr == p->left) ? p->val : p->left->val;
vMax[i] = (nullptr == p->right) ? p->val : p->right->val;
}
for (int i = 0; i < m_c; i++)
{
std::cout << "i " << i;
const auto& p = trees[i];
if ((nullptr != p->left) && mValueIndex.count(p->left->val))
{
const int index = mValueIndex[p->left->val];
if (vMax[index] < p->val)
{
std::cout << "index " << index;
vMin[i] = min(vMin[i], vMin[index]);
p->left = trees[index];
mValueIndex.erase(mValueIndex.find(p->left->val));
}
}
if ((nullptr != p->right) && mValueIndex.count(p->right->val))
{
const int index = mValueIndex[p->right->val];
if (vMin[index] > p->val)
{
std::cout << "r " << index;
vMax[i] = max(vMax[i], vMax[index]);
p->right = trees[index];
mValueIndex.erase(p->right->val);
}
}
std::cout << std::endl;
}
if (mValueIndex.size() > 1) return nullptr;
auto [suc, tmp1, tmp2] = DFS(trees[mValueIndex.begin()->second]);
if (!suc)
{
return nullptr;
}
return trees[mValueIndex.begin()->second];
}
std::tuple<bool, int, int> DFS(TreeNode* root)
{
int iMin = root->val;
int iMax = root->val;
if (nullptr != root->left)
{
auto [suc, iRetMin, iRetMax] = DFS(root->left);
if ((!suc)||( iRetMax >= root->val))
{
return std::make_tuple(false, 0, 0);
}
iMin = iRetMin;
}
if (nullptr != root->right)
{
auto [suc, iRetMin, iRetMax] = DFS(root->right);
if ((!suc) || (iRetMin <= root->val))
{
return std::make_tuple(false, 0, 0);
}
iMax = iRetMax;
}
return std::make_tuple(true, iMin, iMax);
}
int m_c;
};

优化

分析

最后总是要判断是否是合法树,那合并树的时候就不需要判断了。这样会产生新问题:
三个子树成环,两个子树合并成树。 合并了3次符合条件,但不是一棵树,而是两个连通区域。
解法方法如下:
一,判断唯一的树的节点数是否合法。
二,并集查找,看有几个连通区域。
三,判断有几个合法的根(不存在值相同的叶子),从根开始合并。
四,获取树的中顺遍历,看是否是严格递增。且节点数是否正确。

判断合法的根的代码

class Solution {
public:
TreeNode* canMerge(vector<TreeNode*>& trees) {
m_c = trees.size();
unordered_set setLeaf;
for (int i = 0; i < m_c; i++)
{
auto& p = trees[i];
m_mValuePtr[p->val] = p;
if (nullptr != p->left)
{
setLeaf.emplace(p->left->val);
}
if (nullptr != p->right)
{
setLeaf.emplace(p->right->val);
}
}
TreeNode* root = nullptr;
for (int i = 0; i < m_c; i++)
{
if (setLeaf.count(trees[i]->val))
{
continue;//和某个叶子起点重合,不是根
}
if (nullptr != root)
{
return nullptr;//不可能有两个根
}
root = trees[i];
}
if( nullptr == root)
{
return nullptr;
}
DFSBuild(root);
if (m_mValuePtr.size() != 1 )
{
return nullptr;
}
auto [suc, tmp1, tmp2] = DFS(root);
if (!suc)
{
return nullptr;
}
return root;
}
void DFSBuild(TreeNode* root)
{
auto Build = [&](TreeNode*& node)
{
if ((nullptr != node) && (m_mValuePtr.count(node->val)))
{
node = m_mValuePtr[node->val];
m_mValuePtr.erase(node->val);
DFSBuild(node);
}
};
Build(root->left);
Build(root->right);
}
std::tuple<bool, int, int> DFS(TreeNode* root)
{
int iMin = root->val;
int iMax = root->val;
if (nullptr != root->left)
{
auto [suc, iRetMin, iRetMax] = DFS(root->left);
if ((!suc) || (iRetMax >= root->val))
{
return std::make_tuple(false, 0, 0);
}
iMin = iRetMin;
}
if (nullptr != root->right)
{
auto [suc, iRetMin, iRetMax] = DFS(root->right);
if ((!suc) || (iRetMin <= root->val))
{
return std::make_tuple(false, 0, 0);
}
iMax = iRetMax;
}
return std::make_tuple(true, iMin, iMax);
}
unordered_map<int, TreeNode*> m_mValuePtr;
int m_c;
};

中序遍历

class Solution {
public:
TreeNode* canMerge(vector<TreeNode*>& trees) {
m_c = trees.size();
int iNodeCount = 0;
for (int i = 0; i < m_c; i++)
{
const auto& p = trees[i];
m_mValuePtr[p->val] = p;
iNodeCount += (1 + (nullptr != p->left) + (nullptr != p->right));
}
for (int i = 0; i < m_c; i++)
{
auto Build = [&](TreeNode*& node)
{
if ((nullptr != node) && (m_mValuePtr.count(node->val)))
{
node = m_mValuePtr[node->val];
m_mValuePtr.erase(node->val);
}
};
Build(trees[i]->left);
Build(trees[i]->right);
}
if (m_mValuePtr.size() != 1 )
{
return nullptr;
}
vector vNode;
DFSNLR(vNode, m_mValuePtr.begin()->second);
if (vNode.size() + m_c-1 != iNodeCount)
{
return nullptr;
}
for (int i = 1; i < vNode.size(); i++)
{
if (vNode[i] <= vNode[i - 1])
{
return nullptr;
}
}
return m_mValuePtr.begin()->second;
}
void DFSNLR(vector& v,TreeNode* root)
{
if (nullptr == root)
{
return ;
}
DFSNLR(v,root->left);
v.emplace_back(root->val);
DFSNLR(v,root->right);
}
unordered_map<int, TreeNode*> m_mValuePtr;
int m_c;
};

中序遍历优化

vNode我们只需要读取最后一个值,所以可以用一个整形变量pre代替。

 class Solution {public:TreeNode* canMerge(vector<TreeNode*>& trees) {m_c = trees.size();int iNodeCount = 0;for (int i = 0; i < m_c; i++){const auto& p = trees[i];m_mValuePtr[p->val] = p;iNodeCount += (1 + (nullptr != p->left) + (nullptr != p->right));}for (int i = 0; i < m_c; i++){auto Build = [&](TreeNode*& node){if ((nullptr != node) && (m_mValuePtr.count(node->val))){node = m_mValuePtr[node->val];m_mValuePtr.erase(node->val);		}};Build(trees[i]->left);Build(trees[i]->right);}if (m_mValuePtr.size() != 1 ){return  nullptr;}int pre=-1;if (!DFSNLR(m_mValuePtr.begin()->second, pre) || (m_iNodeSize + m_c - 1 != iNodeCount)){return nullptr;}return m_mValuePtr.begin()->second;}bool DFSNLR(TreeNode* root, int& pre){if ((root->left) && (!DFSNLR(root->left, pre))){return false;}m_iNodeSize++;if (root->val <= pre){return false;}pre = root->val;if ((root->right) && (!DFSNLR(root->right, pre))){return false;}return true;}	   int m_iNodeSize = 0;unordered_map<int, TreeNode*> m_mValuePtr;int m_c;};

2023年6月旧版

class Solution {
public:
TreeNode* canMerge(vector<TreeNode*>& trees) {
vector<TreeNode*> vRoot(5 * 10000 + 1);
std::set setNums;
for (auto& ptr : trees)
{
vRoot[ptr->val] = ptr;
setNums.emplace(ptr->val);
if (nullptr != ptr->left)
{
setNums.emplace(ptr->left->val);
}
if (nullptr != ptr->right)
{
setNums.emplace(ptr->right->val);
}
}
int iNodeNum = setNums.size();
for (auto& ptr : trees)
{
if ((nullptr != ptr->left) && (nullptr != vRoot[ptr->left->val]))
{
ptr->left = vRoot[ptr->left->val];
vRoot[ptr->left->val] = nullptr;
}
if ((nullptr != ptr->right) && (nullptr != vRoot[ptr->right->val]))
{
ptr->right = vRoot[ptr->right->val];
vRoot[ptr->right->val] = nullptr;
}
}
TreeNode* pRoot = nullptr;
for (auto ptr : vRoot)
{
if (nullptr == ptr)
{
continue;
}
if (nullptr != pRoot)
{
return nullptr;//两个根
}
pRoot = ptr;
}
if (nullptr == pRoot)
{
return nullptr;
}
if (iNodeNum != DFSNum(pRoot))
{
return nullptr;
}
int iMin, iMax;
bool bRet = DFS(iMin, iMax, pRoot);
return bRet ? pRoot : nullptr;
}
bool DFS(int& iMin, int& iMax, TreeNode* pNode)
{
iMin = iMax = pNode->val;
if ((nullptr != pNode->left))
{
int iMinLeft, iMaxLeft;
if (!DFS(iMinLeft, iMaxLeft, pNode->left))
{
return false;
}
if (iMaxLeft >= pNode->val)
{
return false;
}
iMin = iMinLeft;
}
if ((nullptr != pNode->right))
{
int iMinRight, iMaxRight;
if (!DFS(iMinRight, iMaxRight, pNode->right))
{
return false;
}
if (iMinRight <= pNode->val)
{
return false;
}
iMax = iMaxRight;
}
return true;
}
int DFSNum(TreeNode* pNode)
{
if (nullptr == pNode)
{
return 0;
}
return 1 + DFSNum(pNode->left) + DFSNum(pNode->right);
}
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境:

VS2022 C++17

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

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

相关文章

一种excel多线程并发写sheet的方案

一、背景 有一次项目的需求要求导出excel&#xff0c;并且将不同的数据分别写到不同的sheet中。 二、 方案概述 首先一开始使用easyexcel去导出excel&#xff0c;结果发现导出时间需要3秒左右。于是想着能不能缩短excel导出时间&#xff0c;于是第一次尝试使用异步线程去查询数…

人工智能原理复习--确定性推理

文章目录 上一篇推理概述自然演绎推理合适公式 归结演绎推理归结原理归结反演 提升归结效率下一篇 上一篇 人工智能原理复习–知识表示&#xff08;二&#xff09; 推理概述 推理就是按某种策略由已知判断推出另一判断的思维过程 分类&#xff1a; 演绎推理、归纳推理、默…

kafka 集群 ZooKeeper 模式搭建

Apache Kafka是一个开源分布式事件流平台&#xff0c;被数千家公司用于高性能数据管道、流分析、数据集成和关键任务应用程序 Kafka 官网&#xff1a;Apache Kafka 关于ZooKeeper的弃用 根据 Kafka官网信息&#xff0c;随着Apache Kafka 3.5版本的发布&#xff0c;Zookeeper现…

Christmas is Coming,一起来DIY雪花灯啦

亲爱的朋友们&#xff0c;圣诞节即将到来&#xff01;是不是很期待和家人、朋友一起欢度圣诞夜呢&#xff1f;KCC深圳联合立创开源特别为大家精心挑选了一款氛围感满满的雪花灯&#xff0c;让我们一起创造自己的圣诞礼物~ 彩色丝印PCB版的触摸无极调光雪花灯 哇&#xff0c;立创…

VL53-400激光测距传感器

一、产品简介 先由激光二极管对准目标发射激光脉冲。经目标反射后激光向各方向散射。部分散射光返回到传感器接收器&#xff0c;被光学系统接收后成像到雪崩光电二极管上。雪崩光电二极管是一种内部具有放大功能的光学传感器&#xff0c;因此它能检测极其微弱的光信号。记录并…

6.9 Windows驱动开发:内核枚举进线程ObCall回调

在笔者上一篇文章《内核枚举Registry注册表回调》中我们通过特征码定位实现了对注册表回调的枚举&#xff0c;本篇文章LyShark将教大家如何枚举系统中的ProcessObCall进程回调以及ThreadObCall线程回调&#xff0c;之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用…

02.PostgreSQL 查询处理期间发生了什么?

PostgreSQL 查询处理期间发生了什么&#xff1f; 文中主要内容引用自PostgreSQL指南&#xff1a;内幕探索 查询处理是PostgreSQL中最为复杂的子系统。如PostgreSQL官方文档所述&#xff0c;PostgreSQL支持SQL2011标准中的大多数特性&#xff0c;查询处理子系统能够高效地处理这…

Hdoop学习笔记(HDP)-Part.08 部署Ambari集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

有文件实体的后门无文件实体的后门rootkit后门

有文件实体后门和无文件实体后门&RootKit后门 什么是有文件的实体后门&#xff1a; 在传统的webshell当中&#xff0c;后门代码都是可以精确定位到某一个文件上去的&#xff0c;你可以rm删除它&#xff0c;可以鼠标右键操作它&#xff0c;它是有一个文件实体对象存在的。…

西南科技大学(数据结构A)期末自测练习三

一、填空题&#xff08;每空1分&#xff0c;共10分&#xff09; 1、为解决计算机主机与打印机之间速度不匹配的问题&#xff0c;通常设置一个打印数据缓冲区。主机将要输出的数据依次写入缓冲区&#xff0c;打印机则依次从缓冲区中取出数据&#xff0c;则该换缓冲区的逻辑结构…

【Java Web学习笔记】 2 - CSS入门

项目代码 零、 CSS引出 CSS 教程 官方教学文档 1.在没有CSS之前&#xff0c;我们想要修改HTML元素的样式需要为每个HTML元素单独定义样式属性&#xff0c;费心费力。所以CSS就出现了。 2.使用CSS将HTML页面的内容与样式分离提高web开发的工作效率&#xff08;针对前端开发&a…

一篇短文让你彻底理解什么是逻辑门电路

一、门电路概述 门电路&#xff1a;实现基本运算、复合运算的单元电路&#xff0c;如与门、与非门、或门… 注意&#xff1a;门电路中以高/低电平表示逻辑状态的1/0 正逻辑与负逻辑&#xff1a; 正逻辑&#xff1a;高电平表示1、低电平表示0 负逻辑&#xff1a;高电平表示0、低…

k8s ingress 无法找到端点

文章目录 ingress rule无法找到端点这个注解是什么意思呢&#xff1f;为何不生效呢&#xff1f;端点无法更新&#xff1f;如何确认ingressclass呢&#xff1f;修复端点无法发现的问题多个ingress controller 架构 ingress rule无法找到端点 在vnnox-cn集群创建ingress&#xf…

数据结构(三)——算法和算法分析

&#x1f600;前言 数据结构和算法是计算机科学领域中至关重要的概念。它们为解决实际问题提供了有效的方法和步骤。算法作为解决问题的方法和步骤&#xff0c;在计算机中以指令的有限序列的形式表达。本文将介绍算法的定义、描述和程序设计等方面的内容&#xff0c;帮助您深入…

【FPGA图像处理实战】- 图像基础知识

视频图像处理是FPGA主要应用方向之一&#xff0c;很多FPGA从事或准备进入这一领域&#xff0c;我们现在开始发布新的FPGA实战专栏——FPGA图像处理。 FPGA处理视频图像处理的主要优势是流水线和并行处理运算&#xff0c;特别是现在视频分辨率越来越大&#xff0c;从720p到1080…

二维A*算法

MATLAB2016b可以正常运行 function bidirectional_ASTAR clc; clear; %% 初始化界面 n 11; % field size n x n tiles 20*20的界面 %wallpercent 0.3; % this percent of field is walls 15%的界面作为阻碍物&#xff08;墙&#xff09; cmap [1 1 1; ...% 1 - whit…

linux特殊权限_suid_chattr_umask

3.3 特殊权限 如果一个文件很重要&#xff0c;需要依赖特殊权限避免其被删除。 由于特殊权限会拥有一些“特权”&#xff0c;因而用户若无特殊需要&#xff0c;不应该去打开这些权限&#xff0c;避免安全方面出现严重漏洞&#xff0c;甚至摧毁系统。3个权限是对了执行文件或目…

FPGA串口接收解帧、并逐帧发送有效数据-2

FPGA串口接收解帧、并逐帧发送有效数据 工程实现的功能&#xff1a;FPGA串口接收到串口调试助手发来的数据&#xff0c;将其数据解帧。判断到正确的帧头和帧尾之后&#xff0c;将有效数据存入rx_data中&#xff1b;另一方面发送端将有效数据逐帧发送出去。 参考&#xff1a;正…

【电路笔记】-串联和并联电阻

串联和并联电阻 文章目录 串联和并联电阻1、概述2、串联和并联电阻示例13、串联和并联电阻示例2 电阻器可以无限数量的串联和并联组合连接在一起&#xff0c;形成复杂的电阻电路。 1、概述 在之前的教程中&#xff0c;我们学习了如何将各个电阻器连接在一起以形成串联电阻器网…

linux设置权限_setfacl_getfacl

3.2 设置权限ACL&#xff08;access control list&#xff09; 假设&#xff1a;/data所有者与所属组均为root&#xff0c;在不改变所有者的前提下&#xff0c;要求用户tom对该目录有完全访问权限&#xff08;rwx&#xff09;。只能考虑&#xff1a; 方法一&#xff1a;给/dat…