Hard

题目描述

有一棵树(即一个连通、无向、无环图),由 n 个从 0 到 n - 1 编号的节点和恰好 n - 1 条边组成。每个节点都有一个与之相关联的值,树的根节点是节点 0。

为了表示这棵树,给你一个整数数组 nums 和一个二维数组 edges。其中 nums[i] 表示第 i 个节点的值,edges[j] = [uj, vj] 表示树中节点 uj 和 vj 之间有一条边。

如果 gcd(x, y) == 1,其中 gcd(x, y) 是 x 和 y 的最大公约数,则两个值 x 和 y 互质。

节点 i 的祖先是从节点 i 到根节点的最短路径上的任何其他节点。节点不被认为是其自身的祖先。

返回一个大小为 n 的数组 ans,其中 ans[i] 是距离节点 i 最近的祖先,使得 nums[i] 和 nums[ans[i]] 互质,如果没有这样的祖先,则为 -1。

示例 1:

输入:nums = [2,3,3,2], edges = [[0,1],[1,2],[1,3]]
输出:[-1,0,0,1]
解释:上图中,每个节点的值在括号内。
- 节点 0 没有互质祖先。
- 节点 1 只有一个祖先节点 0。它们的值互质(gcd(2,3) == 1)。
- 节点 2 有两个祖先,节点 1 和 0。节点 1 的值不互质(gcd(3,3) == 3),但节点 0 的值互质(gcd(2,3) == 1),所以节点 0 是最近的有效祖先。
- 节点 3 有两个祖先,节点 1 和 0。它与节点 1 互质(gcd(3,2) == 1),所以节点 1 是其最近的有效祖先。

示例 2:

输入:nums = [5,6,10,2,3,6,15], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]
输出:[-1,0,-1,0,0,0,-1]

约束条件:

  • nums.length == n
  • 1 <= nums[i] <= 50
  • 1 <= n <= 10^5
  • edges.length == n - 1
  • edges[j].length == 2
  • 0 <= uj, vj < n
  • uj != vj

解题思路

这道题需要找到每个节点的最近互质祖先。由于值的范围很小(1到50),我们可以利用这个特点来优化解法。

核心思路:

  1. 预处理互质关系:由于值只在1到50之间,我们可以预先计算出所有值对的互质关系,避免在DFS过程中重复计算gcd。

  2. DFS遍历:从根节点开始进行深度优先搜索,对每个节点维护从根到当前路径上各个值的最近节点信息。

  3. 路径维护技巧:关键优化是维护一个数组 last[value] = node,表示当前路径上值为value的最近节点。当访问一个新节点时:

    • 先查找当前值的所有互质值,找到路径上最近的互质祖先
    • 更新路径信息
    • 递归访问子节点
    • 回溯时恢复路径状态
  4. 时间复杂度优化:由于值的范围小,遍历所有可能的互质值比遍历整条路径更高效。对于每个节点,最多检查50个值,而不需要遍历可能很长的祖先链。

这种方法巧妙地利用了值域小的特点,将时间复杂度从可能的O(n×深度)优化到O(n×50)。

代码实现

class Solution {
public:
    vector<int> getCoprimes(vector<int>& nums, vector<vector<int>>& edges) {
        int n = nums.size();
        vector<vector<int>> graph(n);
        
        // Build adjacency list
        for (auto& edge : edges) {
            graph[edge[0]].push_back(edge[1]);
            graph[edge[1]].push_back(edge[0]);
        }
        
        // Precompute coprime relationships
        vector<vector<int>> coprimes(51);
        for (int i = 1; i <= 50; i++) {
            for (int j = 1; j <= 50; j++) {
                if (__gcd(i, j) == 1) {
                    coprimes[i].push_back(j);
                }
            }
        }
        
        vector<int> result(n, -1);
        vector<pair<int, int>> last(51, {-1, -1}); // {node, depth}
        
        function<void(int, int, int)> dfs = [&](int node, int parent, int depth) {
            int maxDepth = -1, ancestor = -1;
            
            // Find closest coprime ancestor
            for (int val : coprimes[nums[node]]) {
                if (last[val].first != -1 && last[val].second > maxDepth) {
                    maxDepth = last[val].second;
                    ancestor = last[val].first;
                }
            }
            
            result[node] = ancestor;
            
            // Save current state and update
            auto prev = last[nums[node]];
            last[nums[node]] = {node, depth};
            
            // Visit children
            for (int child : graph[node]) {
                if (child != parent) {
                    dfs(child, node, depth + 1);
                }
            }
            
            // Restore state
            last[nums[node]] = prev;
        };
        
        dfs(0, -1, 0);
        return result;
    }
};
class Solution:
    def getCoprimes(self, nums: List[int], edges: List[List[int]]) -> List[int]:
        import math
        
        n = len(nums)
        graph = [[] for _ in range(n)]
        
        # Build adjacency list
        for u, v in edges:
            graph[u].append(v)
            graph[v].append(u)
        
        # Precompute coprime relationships
        coprimes = [[] for _ in range(51)]
        for i in range(1, 51):
            for j in range(1, 51):
                if math.gcd(i, j) == 1:
                    coprimes[i].append(j)
        
        result = [-1] * n
        last = [(-1, -1)] * 51  # (node, depth)
        
        def dfs(node, parent, depth):
            max_depth = -1
            ancestor = -1
            
            # Find closest coprime ancestor
            for val in coprimes[nums[node]]:
                if last[val][0] != -1 and last[val][1] > max_depth:
                    max_depth = last[val][1]
                    ancestor = last[val][0]
            
            result[node] = ancestor
            
            # Save current state and update
            prev = last[nums[node]]
            last[nums[node]] = (node, depth)
            
            # Visit children
            for child in graph[node]:
                if child != parent:
                    dfs(child, node, depth + 1)
            
            # Restore state
            last[nums[node]] = prev
        
        dfs(0, -1, 0)
        return result
public class Solution {
    public int[] GetCoprimes(int[] nums, int[][] edges) {
        int n = nums.Length;
        var graph = new List<int>[n];
        for (int i = 0; i < n; i++) {
            graph[i] = new List<int>();
        }
        
        // Build adjacency list
        foreach (var edge in edges) {
            graph[edge[0]].Add(edge[1]);
            graph[edge[1]].Add(edge[0]);
        }
        
        // Precompute coprime relationships
        var coprimes = new List<int>[51];
        for (int i = 0; i <= 50; i++) {
            coprimes[i] = new List<int>();
        }
        
        for (int i = 1; i <= 50; i++) {
            for (int j = 1; j <= 50; j++) {
                if (Gcd(i, j) == 1) {
                    coprimes[i].Add(j);
                }
            }
        }
        
        var result = new int[n];
        Array.Fill(result, -1);
        var last = new (int node, int depth)[51];
        Array.Fill(last, (-1, -1));
        
        void Dfs(int node, int parent, int depth) {
            int maxDepth = -1, ancestor = -1;
            
            // Find closest coprime ancestor
            foreach (int val in coprimes[nums[node]]) {
                if (last[val].node != -1 && last[val].depth > maxDepth) {
                    maxDepth = last[val].depth;
                    ancestor = last[val].node;
                }
            }
            
            result[node] = ancestor;
            
            // Save current state and update
            var prev = last[nums[node]];
            last[nums[node]] = (node, depth);
            
            // Visit children
            foreach (int child in graph[node]) {
                if (child != parent) {
                    Dfs(child, node, depth + 1);
                }
            }
            
            // Restore state
            last[nums[node]] = prev;
        }
        
        Dfs(0, -1, 0);
        return result;
    }
    
    private int Gcd(int a, int b) {
        return b == 0 ? a : Gcd(b, a % b);
    }
}
var getCoprimes = function(nums, edges) {
    const n = nums.length;
    const graph = Array(n).fill(null).map(() => []);
    
    for (const [u, v] of edges) {
        graph[u].push(v);
        graph[v].push(u);
    }
    
    const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
    
    const result = Array(n).fill(-1);
    const ancestors = Array(51).fill(null).map(() => []);
    
    const dfs = (node, parent) => {
        let maxDepth = -1;
        let closestAncestor = -1;
        
        for (let val = 1; val <= 50; val++) {
            if (ancestors[val].length > 0 && gcd(nums[node], val) === 1) {
                const [ancestorNode, depth] = ancestors[val][ancestors[val].length - 1];
                if (depth > maxDepth) {
                    maxDepth = depth;
                    closestAncestor = ancestorNode;
                }
            }
        }
        
        result[node] = closestAncestor;
        
        ancestors[nums[node]].push([node, ancestors[nums[node]].length]);
        
        for (const child of graph[node]) {
            if (child !== parent) {
                dfs(child, node);
            }
        }
        
        ancestors[nums[node]].pop();
    };
    
    dfs(0, -1);
    return result;
};

复杂度分析

复杂度类型分析
时间复杂度O(n × 50 + 50²) = O(n),其中 n 为节点数。预处理互质关系需要 O(50²),DFS 遍历每个节点时最多检查50个值
空间复杂度O(n + 50²) = O(n),用于存储图的邻接表、互质关系表和递归栈空间