当前位置: 首页 > news >正文

力扣:2458. 移除子树后的二叉树高度(dfs序)

给你一棵 二叉树 的根节点 root ,树中有 n 个节点。每个节点都可以被分配一个从 1 到 n 且互不相同的值。另给你一个长度为 m 的数组 queries 。

你必须在树上执行 m 个 独立 的查询,其中第 i 个查询你需要执行以下操作:

  • 从树中 移除 以 queries[i] 的值作为根节点的子树。题目所用测试用例保证 queries[i]  等于根节点的值。

返回一个长度为 m 的数组 answer ,其中 answer[i] 是执行第 i 个查询后树的高度。

注意:

  • 查询之间是独立的,所以在每个查询执行后,树会回到其 初始 状态。
  • 树的高度是从根到树中某个节点的 最长简单路径中的边数 。

示例 1:

输入:root = [1,3,4,2,null,6,5,null,null,null,null,null,7], queries = [4]
输出:[2]
解释:上图展示了从树中移除以 4 为根节点的子树。
树的高度是 2(路径为 1 -> 3 -> 2)。

示例 2:

输入:root = [5,8,9,2,1,3,7,4,6], queries = [3,2,4,8]
输出:[3,2,3,2]
解释:执行下述查询:
- 移除以 3 为根节点的子树。树的高度变为 3(路径为 5 -> 8 -> 2 -> 4)。
- 移除以 2 为根节点的子树。树的高度变为 2(路径为 5 -> 8 -> 1)。
- 移除以 4 为根节点的子树。树的高度变为 3(路径为 5 -> 8 -> 2 -> 6)。
- 移除以 8 为根节点的子树。树的高度变为 2(路径为 5 -> 9 -> 3)。

提示:

  • 树中节点的数目是 n
  • 2 <= n <= 105
  • 1 <= Node.val <= n
  • 树中的所有值 互不相同
  • m == queries.length
  • 1 <= m <= min(n, 104)
  • 1 <= queries[i] <= n
  • queries[i] != root.val

最初的思路很简单:

一遍 dfs 走完全程,枚举 queries里面的所有元素 i,在 i 结点被删除的情况下考虑最长路径。每一次枚举,dfs都从根节点出发向下搜索,先走左子树,再走右子树,每次向下搜索时都将深度+1,如果在搜索的过程中遇到了当前删除的结点 i 就回溯,直到当前结点不可向下 (一个子树为空,另一个子树为当前被删除,)  或者当前结点为叶子结点 (左右子树都为空),搜索停止,判断当前深度是否为最大,然后return回溯。然而 35/40 时间超限。

参考代码:

class Solution {
public:vector<int> ans;vector<int> treeQueries(TreeNode* root, vector<int>& queries) {for (auto it : queries) {ans.push_back(0);if (it == root->val) {//删除的结点为根节点continue;}else {if (root->left != nullptr && root->left->val != it) {//左子树不空且左子树的节点值与删除的结点值不等dfs(root->left, 1, it);}if (root->right != nullptr && root->right->val != it) {//右子树不空且右子树的节点值与删除的结点值不等dfs(root->right, 1, it);}}}return ans;}void dfs(TreeNode* x, int sum,int re) {if (x->left == nullptr && x->right == nullptr) {//到达叶子结点int k = ans.back();if (sum > k) {ans.pop_back();ans.push_back(sum);}return;}else if ((x->left == nullptr && x->right->val == re) || (x->right == nullptr && x->left->val == re)) {// 未到达叶子结点但不可向下int k = ans.back();if (sum > k) {ans.pop_back();ans.push_back(sum);}return;}else {if (x->left != nullptr && x->left->val != re) {//左子树不空且左子树的节点值与删除的结点值不等dfs(x->left, sum + 1, re);}if (x->right != nullptr && x->right->val != re) {//右子树不空且右子树的节点值与删除的结点值不等dfs(x->right, sum + 1, re);}}}
};

接下来就是思考如何优化时间的问题:

在上述的代码中 树上结点数n如果=1e5,queries 数组长度如果达到1e4,那么时间复杂度无疑是极大的。

而换一种想法,将某棵子树删除后求最大路径,就等同于在剩下的树中求最大深度。而将子树删除就等同于将子树上所有的结点标记,那么就需要2次 dfs,第一次dfs获取所有结点的深度并记录在dep数组中,然后枚举 queries数组中的元素 i,第二次 dfs搜索以 i 结点为根的子树,将子树上的所有结点标记,标记完成后遍历 dep数组,在未标记的dep数组中找出最大深度。

然而依旧时间超限。。。

参考代码:

class Solution {
public:vector<int> ans;static const int N = 1e5 + 10;int dep[N];//每个结点的深度bool flag[N];int n = 0;map<int, TreeNode*> mp;vector<int> treeQueries(TreeNode* root, vector<int>& queries) {dfs(root,0);//确定每个结点对应的深度for (auto it : queries) {memset(flag, 0, sizeof flag);dfs(mp[it]);int k = 0;for (int i = 1;i <= n;i++) {// 遍历所有结点的深度if (!flag[i]) {//在未标记的结点中找最大深度k = max(k, dep[i]);}}ans.push_back(k);}return ans;}void dfs(TreeNode* x, int sum) {dep[x->val] = sum;mp[x->val] = x;n++;if (x->left == nullptr && x->right == nullptr) {return;}else{if (x->left != nullptr) {dfs(x->left, sum + 1);}if (x->right != nullptr) {dfs(x->right, sum + 1);}}}void dfs(TreeNode* x) {flag[x->val] = 1;if (x->left == nullptr && x->right == nullptr) {return;}else {if (x->left != nullptr) {dfs(x->left);}if (x->right != nullptr) {dfs(x->right);}}}

再对上述代码进行分析:

树上结点数n如果=1e5,queries 数组中的元素如果都比较接近根节点,那么每次标记所走的 dfs其时间复杂度同样很大,在时间上并不会很占优。

然而写到这里就不难发现,上述代码中所删除的子树在 dfs序 中无疑是一个整体,如果这时候通过dfs获取整棵树的 dfs序 再通过一维数组将 dfs序 罗列出来,我们想删除的子树实际上就是这个一维数组上的一个片段。举例如下图:

将片段删除后,整个一维数组就剩下左右两段。如果此时我们将上述代码获取到的 dep数组改写一下:原本dep数组表示结点值为 i 其对应深度,改写成 dfs序号为 i 的结点深度;将dep数组与dfs序数组对应起来,那么此时的最长路径就是在 dfs序的一维数组剩下的左右两侧中找最大深度。

在最后找最大深度时建议使用前后缀比大小,线性遍历dep数组会时间超限。

AC代码:

C++版:

class Solution {
public:static const int N = 1e5 + 10;int dep[N];//dfs序号为i的节点的深度int dfn[N];//第i个节点的dfs序号int ch[N];//以dfs序号为i的节点的子树大小int n = 1;int suf[N];//后缀最大值int pre[N];//前缀最大值vector<int> ans;vector<int> treeQueries(TreeNode* root, vector<int>& queries) {ch[1] = dfs(root, 0);//构建前缀最大值数组pre[0] = 0;for (int i = 1;i < n;i++) {pre[i] = max(pre[i - 1], dep[i]);}//构建后缀最大值数组suf[n] = 0;for (int i = n - 1;i >= 1;i--) {suf[i] = max(suf[i + 1], dep[i]);}for (auto it : queries) {//处理查询int l = dfn[it];int r = l + ch[l] - 1;int k = max(pre[l - 1], suf[r + 1]);ans.push_back(k);}return ans;}int dfs(TreeNode* x, int sum) {if (x == nullptr) {return 0;}dfn[x->val] = n;dep[n] = sum;int idx = n;n++;int l = dfs(x->left, sum + 1);int r = dfs(x->right, sum + 1);ch[idx] = l + r + 1;return ch[idx];}
};

java版本:

import java.util.ArrayList;class Solution {static final int N=100010;ArrayList<Integer> arr=new ArrayList<>();int ch[]=new int[N];int dfn[]=new int[N];int dep[]=new int[N];public int n=1;int pre[]=new int[N];int suf[]=new int[N];public int[] treeQueries(TreeNode root, int[] queries) {ch[1]=dfs(root,0);pre[0]=0;for(int i=1;i<n;i++){pre[i]=Math.max(dep[i],pre[i-1]);}suf[n]=0;for(int i=n-1;i>=1;i--){suf[i]=Math.max(dep[i],suf[i+1]);}for(int i:queries) {int l = dfn[i];int r = l + ch[l] - 1;int k = Math.max(pre[l - 1], suf[r + 1]);arr.add(k);}int ans[]=arr.stream().mapToInt(Integer::intValue).toArray();return ans;}int dfs(TreeNode x,int sum){if(x==null){return 0;}dfn[x.val]=n;int idx=n;dep[idx]=sum;n++;int l=dfs(x.left,sum+1);int r=dfs(x.right,sum+1);ch[idx]=l+r+1;return ch[idx];}
}


文章转载自:

http://kVvj5yQu.wjtxt.cn
http://ZJI8MYXI.wjtxt.cn
http://OgPzcMed.wjtxt.cn
http://KAlreXTL.wjtxt.cn
http://0kcx2jUN.wjtxt.cn
http://u8wAMB10.wjtxt.cn
http://gGCtwy2l.wjtxt.cn
http://xSyGhuFz.wjtxt.cn
http://RLY2bFO6.wjtxt.cn
http://2YDkHRMv.wjtxt.cn
http://ESiQATBs.wjtxt.cn
http://Mu6Sbtm9.wjtxt.cn
http://QJtqeIyj.wjtxt.cn
http://XV7eGktU.wjtxt.cn
http://K7FQiJLE.wjtxt.cn
http://QHzsGN6q.wjtxt.cn
http://rgEUuFLO.wjtxt.cn
http://0IHU8537.wjtxt.cn
http://PXYrTXYK.wjtxt.cn
http://RuFrRwjw.wjtxt.cn
http://oBdROZ8c.wjtxt.cn
http://YD1XJgOd.wjtxt.cn
http://fniotth1.wjtxt.cn
http://0f7MKsvI.wjtxt.cn
http://Bf1tHnFo.wjtxt.cn
http://NIViFtsx.wjtxt.cn
http://PPS2VqyM.wjtxt.cn
http://52YMbzmS.wjtxt.cn
http://dmQTlLOg.wjtxt.cn
http://0AwYgTia.wjtxt.cn
http://www.dtcms.com/a/362420.html

相关文章:

  • leetcode111. 二叉树的最小深度
  • 前缀和之距离和
  • 基于SQLite的智能图片压缩存储系统:代码解析与实战应用
  • Time-MOE 音频序列分类任务
  • form表达和实体类通常有什么不同
  • C#中的克隆:从理论到实践
  • Elasticsearch Java开发(SpringBoot)
  • 从零开始的云计算生活——第五十六天,临深履薄,kubernetes模块之etcd备份恢复和集群升级指南
  • Prettier代码格式化工具测评:支持JS/TS/Vue多语言,兼容ESLint实现团队代码格式统一
  • 在 PySpark 中解锁窗口函数的力量,实现高级数据转换
  • 什么是Token?——理解自然语言处理中的基本单位
  • 毕业项目推荐:68-基于yolov8/yolov5/yolo11的水稻虫害检测识别系统(Python+卷积神经网络)
  • Python OpenCV图像处理与深度学习: Python OpenCV图像配准入门
  • 深度学习中的数据增强实战:基于PyTorch的图像分类任务优化
  • 云计算学习100天-第43天-cobbler
  • 【linux仓库】万物至简的设计典范:如何用‘文件’这一个概念操纵整个Linux世界?
  • 【数据分享】土地利用shp数据分享-内蒙古
  • Python应用——ffmpeg处理音视频的常见场景
  • 谷歌AdSense套利是什么?怎么做才能赚到钱
  • 安卓QQ闪照获取软件(支持TIM)
  • 各省市信息化项目管理办法中的网络安全等级保护如何规定的?
  • 智能化企业级CRM系统开发实战:飞算JavaAI全流程体验
  • 【音视频】火山引擎实时、低延时拥塞控制算法的优化实践
  • 在 Delphi 5 中获取 Word 文档页数的方法
  • ⸢ 肆 ⸥ ⤳ 默认安全:安全建设方案 ➭ a.信息安全基线
  • 在线宠物用品|基于vue的在线宠物用品交易网站(源码+数据库+文档)
  • 从Web2到Web3:一场重塑数字未来的“静默革命”
  • OpenMMLab 模型部署利器:MMDeploy 详细介绍
  • 小学一到六年级语文/英语/数学作业出题布置网站源码 支持生成PDF和打印
  • Windows 电脑发现老是自动访问外网的域名排障步骤