力扣每日一题——连接两棵树后最大目标节点数目 ||

article/2025/8/25 2:46:13

目录

题目链接:3373. 连接两棵树后最大目标节点数目 II - 力扣(LeetCode)

题目描述

解法一:​​双树贡献分离法​​

Java写法:

C++写法:

运行时间

时间复杂度和空间复杂度

总结


题目链接:3373. 连接两棵树后最大目标节点数目 II - 力扣(LeetCode)

注:下述题目描述和示例均来自力扣

题目描述

        有两棵 无向 树,分别有 n 和 m 个树节点。两棵树中的节点编号分别为[0, n - 1] 和 [0, m - 1] 中的整数。

        给你两个二维整数 edges1 和 edges2 ,长度分别为 n - 1 和 m - 1 ,其中 edges1[i] = [ai, bi] 表示第一棵树中节点 ai 和 bi 之间有一条边,edges2[i] = [ui, vi] 表示第二棵树中节点 ui 和 vi 之间有一条边。

        如果节点 u 和节点 v 之间路径的边数是偶数,那么我们称节点 u 是节点 v 的 目标节点 。注意 ,一个节点一定是它自己的 目标节点 。

        请你返回一个长度为 n 的整数数组 answer ,answer[i] 表示将第一棵树中的一个节点与第二棵树中的一个节点连接一条边后,第一棵树中节点 i 的 目标节点 数目的 最大值 。

注意 ,每个查询相互独立。意味着进行下一次查询之前,你需要先把刚添加的边给删掉。

示例 1:

输入:edges1 = [[0,1],[0,2],[2,3],[2,4]], edges2 = [[0,1],[0,2],[0,3],[2,7],[1,4],[4,5],[4,6]]

输出:[8,7,7,8,8]

解释:

  • 对于 i = 0 ,连接第一棵树中的节点 0 和第二棵树中的节点 0 。
  • 对于 i = 1 ,连接第一棵树中的节点 1 和第二棵树中的节点 4 。
  • 对于 i = 2 ,连接第一棵树中的节点 2 和第二棵树中的节点 7 。
  • 对于 i = 3 ,连接第一棵树中的节点 3 和第二棵树中的节点 0 。
  • 对于 i = 4 ,连接第一棵树中的节点 4 和第二棵树中的节点 4 。

示例 2:

输入:edges1 = [[0,1],[0,2],[0,3],[0,4]], edges2 = [[0,1],[1,2],[2,3]]

输出:[3,6,6,6,6]

解释:

对于每个 i ,连接第一棵树中的节点 i 和第二棵树中的任意一个节点。

提示:

  • 2 <= n, m <= 10^5
  • edges1.length == n - 1
  • edges2.length == m - 1
  • edges1[i].length == edges2[i].length == 2
  • edges1[i] = [ai, bi]
  • 0 <= ai, bi < n
  • edges2[i] = [ui, vi]
  • 0 <= ui, vi < m
  • 输入保证 edges1 和 edges2 都表示合法的树。

解法一:​​双树贡献分离法​

        这道题的解法可以称为​​双树贡献分离法​​。咱们分两部分来说,解核心思路和具体实现。

        首先,问题的核心在于两棵树之间只能连一条边的情况下,如何最大化第一棵树每个节点的目标节点数。这里的关键在于发现:连接后的贡献可以拆分为原本树内的贡献和通过新边获得的第二棵树贡献。举个例子,假设我们在第一棵树的节点A和第二棵树的节点B之间连边,那么A的目标节点数就等于A在原本树里的可达节点数加上B在另一棵树里能带来的额外节点数。

        这里整个过程分两步走:第一步要算出第一棵树每个节点自身在k步内能到达多少个目标节点,这一步可以用DFS遍历整棵树,记录每个节点在限定步数内能找到的节点数。第二步要找出第二棵树中哪个节点能在k-1步内覆盖最多的节点(因为跨树连接需要消耗一步边权),这时候同样用DFS遍历第二棵树的所有节点,找到最大值。

        这里有个比较巧妙的地方:当k=0时根本不能跨树连接,所以这时候第二棵树的贡献直接为零;当k≥1时,跨树后的剩余步数是k-1,这时候只要找到第二棵树中能覆盖最多节点的那个节点即可。整个过程不需要真正连接所有可能的节点对,而是通过预处理两棵树各自的贡献,最后直接相加得到结果。

        实现时要注意两点:1. 使用DFS时要避免重复访问父节点,通过记录父节点指针来防止回头路;2. 在计算第二棵树的贡献时,只需要保留最大值而不需要记录每个节点的贡献,这样能节省内存空间。整个过程的时间复杂度主要取决于两棵树的节点数和k值的大小,属于典型的树遍历问题优化方案。

        这种解法把看似复杂的连接问题拆解成了两个独立的子问题,最后通过简单相加得到结果,既避免了暴力枚举所有可能的连接方式,又保证了算法效率,算是个典型的分治思想在树结构中的应用。

Java写法:

import java.util.*;public class Solution {public int[] maxTargetNodes(int[][] edges1, int[][] edges2) {List<List<Integer>> tree1 = buildTree(edges1);List<List<Integer>> tree2 = buildTree(edges2);// 计算两棵树的层级奇偶分布int[] layerCount1 = computeLayerCounts(tree1);int[] layerCount2 = computeLayerCounts(tree2);// 第二棵树的最大贡献int maxSecond = Math.max(layerCount2[0], layerCount2[1]);// 获取第一棵树每个节点的层级int[] nodeLayers = getNodeLayers(tree1);// 计算结果int[] ans = new int[tree1.size()];for (int i = 0; i < ans.length; i++) {int currentLayer = nodeLayers[i] % 2;ans[i] = (currentLayer == 0 ? layerCount1[0] : layerCount1[1]) + maxSecond;}return ans;}// 构建邻接表private List<List<Integer>> buildTree(int[][] edges) {int n = edges.length + 1;List<List<Integer>> tree = new ArrayList<>();for (int i = 0; i < n; i++) tree.add(new ArrayList<>());for (int[] e : edges) {tree.get(e[0]).add(e[1]);tree.get(e[1]).add(e[0]);}return tree;}// 计算奇偶层节点数(BFS实现)private int[] computeLayerCounts(List<List<Integer>> tree) {int[] layers = new int[tree.size()];Arrays.fill(layers, -1);Queue<Integer> queue = new LinkedList<>();queue.add(0);layers[0] = 0;while (!queue.isEmpty()) {int u = queue.poll();for (int v : tree.get(u)) {if (layers[v] == -1) {layers[v] = layers[u] + 1;queue.add(v);}}}int even = 0, odd = 0;for (int layer : layers) {if (layer % 2 == 0) even++;else odd++;}return new int[]{even, odd};}// 获取每个节点的层级(BFS实现)private int[] getNodeLayers(List<List<Integer>> tree) {int[] layers = new int[tree.size()];Arrays.fill(layers, -1);Queue<Integer> queue = new LinkedList<>();queue.add(0);layers[0] = 0;while (!queue.isEmpty()) {int u = queue.poll();for (int v : tree.get(u)) {if (layers[v] == -1) {layers[v] = layers[u] + 1;queue.add(v);}}}return layers;}
}

C++写法:

#include <vector>
#include <queue>
using namespace std;class Solution {
public:vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {auto tree1 = buildTree(edges1);auto tree2 = buildTree(edges2);auto [even1, odd1] = computeLayerCounts(tree1);auto [even2, odd2] = computeLayerCounts(tree2);int maxSecond = max(even2, odd2);auto nodeLayers = getNodeLayers(tree1);vector<int> ans(tree1.size());for (int i = 0; i < ans.size(); ++i) {ans[i] = (nodeLayers[i] % 2 ? odd1 : even1) + maxSecond;}return ans;}private:vector<vector<int>> buildTree(vector<vector<int>>& edges) {int n = edges.size() + 1;vector<vector<int>> tree(n);for (auto& e : edges) {tree[e[0]].push_back(e[1]);tree[e[1]].push_back(e[0]);}return tree;}pair<int, int> computeLayerCounts(vector<vector<int>>& tree) {vector<int> layers(tree.size(), -1);queue<int> q;q.push(0);layers[0] = 0;while (!q.empty()) {int u = q.front();q.pop();for (int v : tree[u]) {if (layers[v] == -1) {layers[v] = layers[u] + 1;q.push(v);}}}int even = 0, odd = 0;for (int l : layers) {(l % 2 == 0) ? even++ : odd++;}return {even, odd};}vector<int> getNodeLayers(vector<vector<int>>& tree) {vector<int> layers(tree.size(), -1);queue<int> q;q.push(0);layers[0] = 0;while (!q.empty()) {int u = q.front();q.pop();for (int v : tree[u]) {if (layers[v] == -1) {layers[v] = layers[u] + 1;q.push(v);}}}return layers;}
};

运行时间

时间复杂度和空间复杂度

  • ​时间复杂度​​:O(nk + mk)(DFS)或 O(n² + m²)(BFS)。
  • ​空间复杂度​​:O(n + m + k)



总结

        阿巴阿巴


http://www.hkcw.cn/article/oudBmMYjRo.shtml

相关文章

国芯思辰| 国产四通道24位生理电采集模拟前端AFE全面替换ADS1294R,心电贴性能再飞跃

心电贴作为一种便捷的可穿戴医疗设备&#xff0c;能够实时、连续地监测心电信号&#xff0c;为心脏健康保驾护航。其中高性能、低功耗的AFE芯片是关键核心部件&#xff0c;集成国产四通道24位AFE的三导联心电贴&#xff0c;不仅更小、更轻、还更准、续航更长、监测维度更多&…

Linux线程池(上)(33)

文章目录 前言一、线程池的概念池化技术线程池的优点线程池的使用场景 二、线程池的实现v1版本v2版本 总结 前言 终于要结束了&#xff0c;写完这篇再来篇有关锁的文章&#xff0c;我们就可以结束 Linux系统编程 部分啦&#xff01; 线程池是一种管理线程的机制&#xff0c;它可…

Python 之图片添加时间戳水印

依赖安装 pip install pillow 原图 随便找个图片作为原图即可&#xff08;比如截图一个桌面背景图&#xff09;。 test.png 添加水印 from PIL import Image, ImageDraw, ImageFont import datetimedef add_timestamp_watermark(image_path, font_size24):# 打开图片imag…

jenkins集成gitlab实现自动构建

jenkins集成gitlab实现自动构建 前面我们已经部署了Jenkins和gitlab&#xff0c;本文介绍将二者结合使用 项目源码上传至gitee提供公网访问&#xff1a;https://gitee.com/ye-xiao-tian/my-webapp 1、创建一个群组和项目 2、添加ssh密钥 #生成密钥 [rootgitlab ~]# ssh-keyge…

深入了解linux系统—— 库的链接和加载

一、目标文件 我们知道源文件经过编译链接形成可执行程序&#xff0c;在Windows下这两个步骤被IDEA封装的很完美&#xff0c;我们使用起来也非常方便&#xff1b; 在Linux中&#xff0c;我们可以通过gcc编译器来完成编译链接这一系列操作。 而源文件经过编译形成.o文件&…

Cesium 8 ,在 Cesium 上实现雷达动画和车辆动画效果,并控制显示和隐藏

目录 ✨前言 一、功能背景 1.1 核心功能概览 1.2 技术栈与工具 二、车辆动画 2.1 模型坐标 2.2 组合渲染 2.3 显隐状态 2.4 模型文件 三、雷达动画 3.1 创建元素 3.2 动画解析 3.3 坐标联动 3.4 交互事件 四、完整代码 4.1 属性参数 4.2 逻辑代码 加载车辆动画…

ElasticSearch简介及常用操作指南

一. ElasticSearch简介 ElasticSearch 是一个基于 Lucene 构建的开源、分布式、RESTful 风格的搜索和分析引擎。 1. 核心功能 强大的搜索能力 它能够提供全文检索功能。例如&#xff0c;在海量的文档数据中&#xff0c;可以快速准确地查找到包含特定关键词的文档。这在处理诸如…

Transformer《Attention is all you need》

发布时间&#xff1a;2017/06/12 发布单位&#xff1a;Google、多伦多⼤学 简单摘要&#xff1a;直译为“变换器”&#xff0c;⼀种采⽤⾃注意⼒机制的深度学习模型&#xff0c;按照输⼊数据各部分重要 性不同⽽分配不同权重。⼴泛⽤于NLP和CV领域。 阅读重点&#xff1a;s…

html+css+js趣味小游戏~HexGL赛车竞速(附源码)

下面是一个简单的记忆卡片配对游戏的完整代码&#xff0c;使用HTML、CSS和JavaScript实现&#xff1a; html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"wid…

VL 中间语言核心技术架构:构建全链路开发生态

一、VL 中间语言核心架构&#xff1a;全链路开发的三大关键层级 在企业级应用开发面临效率与技术深度双重挑战的背景下&#xff0c;iVX 自主研发的 VL&#xff08;Visual Language&#xff09;中间语言体系&#xff0c;通过 "可视化建模 - 语义编译 - 多端适配" 三大…

GPU 图形计算综述 (二):固定管线

在计算机图形学中&#xff0c;图形管线&#xff08;Graphics Pipeline&#xff09;是指通过一系列软硬件算法&#xff0c;将三维空间中的物体表征&#xff0c;转换为二维空间的物体表征的过程。一般通过3D网格&#xff08;Mesh&#xff09;等图元&#xff08;Primitive&#xf…

Manus数据手套:赋能人形机器人遥操作与AI数据训练的创新力量

人形机器人技术与AI技术正在进入蓬勃发展的黄金时代。特斯拉高调发布其即将推向市场的人形机器人Optimus&#xff0c;引发全球瞩目&#xff1b;与此同时&#xff0c;国内人形机器人产业也如雨后春笋般迅速崛起&#xff0c;展现出强劲的发展势头。在这一技术浪潮中&#xff0c;M…

C# 控制台程序实现定时自动退出

一、基础实现方式&#xff1a;同步阻塞等待 通过Thread.Sleep暂停主线程&#xff0c;适合简单场景&#xff08;需阻塞当前线程&#xff09;。 static void Main(string[] args) { Console.WriteLine("程序启动&#xff0c;5秒后自动退出..."); Thread.Slee…

【笔记】suna部署之获取 Firecrawl API key

#工作记录 Firecrawl 一、前期准备 在进行 Suna 部署时&#xff0c;获取 Firecrawl API key 是其中一个关键步骤。Firecrawl 是一款功能强大的工具&#xff0c;在 Suna 项目中可发挥重要作用&#xff0c;比如助力数据获取等相关任务。 二、获取步骤 &#xff08;一&#xff…

花哨桌面 V 3.0.0 (火影忍者版)

废话不多说,直接上链接,源码在之前版本的帖子里,本次主要修改了部分元素. 功能也不描述了哦 效果图

西门子PLC结构化编程_优化后的调节阀标准块

文章目录 前言一、功能概述二、程序编写1.新建数据类型“5_RegvalveType”2.新建FB块“6_Regvalve”3.SCL和LAD混合编程 总结 前言 在之前的文章中&#xff0c;分享过一个基于SCL语言实现的调节阀控制块西门子PLC常用底层逻辑块分享_调节阀&#xff0c;在实际应用过程中&#…

react-color-palette源码解析

项目中用到了react-color-palette组件&#xff0c;以前对第三方组件都是不求甚解&#xff0c;这次想了解一下其实现细节。 简介 react-color-palette 是一个用于创建颜色调色板的 React 组件。它提供了一个简单易用的接口&#xff0c;让开发者可以轻松地创建和管理颜色调色板。…

(一)视觉——工业相机(以海康威视为例)

一、工业相机介绍 工业相机是机器视觉系统中的一个关键组件&#xff0c;其最本质的功能就是将光信号转变成有序的电信号。选择合适的相机也是机器视觉系统设计中的重要环节&#xff0c;相机的选择不仅直接决定所采集到的图像分辨率、图像质量等&#xff0c;同时也与整个系统的运…

PnP(Perspective-n-Point)算法 | 用于求解已知n个3D点及其对应2D投影点的相机位姿

什么是PnP算法&#xff1f; PnP 全称是 Perspective-n-Point&#xff0c;中文叫“n点透视问题”。它的目标是&#xff1a; 已知一些空间中已知3D点的位置&#xff08;世界坐标&#xff09;和它们对应的2D图像像素坐标&#xff0c;求解摄像机的姿态&#xff08;位置和平移&…

C++核心编程_4.5 运算符重载_4.5.1 加号运算符重载

#include <iostream> #include <string> using namespace std;/* ### 4.5 运算符重载 运算符重载概念&#xff1a;对已有的运算符重新进行定义&#xff0c;赋予其另一种功能&#xff0c;以适应不同的数据类型 *//* 4.5.1 加号运算符重载 作用&#xff1a;实现两…