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

目录

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

题目描述

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

Java写法:

C++写法:

运行时间

时间复杂度和空间复杂度

总结


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

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

题目描述

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

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

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

请你返回一个长度为 n 的整数数组 answeranswer[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
  • 输入保证 edges1edges2 都表示合法的树。

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

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

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

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

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

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

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

Java写法:

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++写法:

cpp 复制代码
#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)



总结

阿巴阿巴

相关推荐
周杰伦_Jay13 分钟前
【图文详解】强化学习核心框架、数学基础、分类、应用场景
人工智能·科技·算法·机器学习·计算机视觉·分类·数据挖掘
violet-lz28 分钟前
Linux静态库与共享库(动态库)全面详解:从创建到应用
算法
贝塔实验室28 分钟前
ADMM 算法的基本概念
算法·数学建模·设计模式·矩阵·动态规划·软件构建·傅立叶分析
2351638 分钟前
【LeetCode】3. 无重复字符的最长子串
java·后端·算法·leetcode·职场和发展
微笑尅乐1 小时前
神奇的位运算——力扣136.只出现一次的数字
java·算法·leetcode·职场和发展
自信的小螺丝钉2 小时前
Leetcode 155. 最小栈 辅助栈
leetcode·
吃着火锅x唱着歌2 小时前
LeetCode 3105.最长的严格递增或递减子数组
算法·leetcode·职场和发展
测试19982 小时前
Web自动化测试之测试用例流程设计
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
小卡皮巴拉2 小时前
【笔试强训】Day1
开发语言·数据结构·c++·算法
初圣魔门首席弟子2 小时前
switch缺少break出现bug
c++·算法·bug