力扣每日一题——连接两棵树后最大目标节点数目 ||
目录
题目链接: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 <=
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)。
总结
阿巴阿巴