Medium
题目描述
有一个由 n 个节点组成的无向树,节点标号从 1 到 n,以节点 1 为根。树由长度为 n - 1 的二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示节点 ui 和 vi 之间有一条边。
初始时,所有边的权重都是 0。你必须为每条边分配权重 1 或 2。
任意两个节点 u 和 v 之间路径的代价是连接它们路径上所有边的权重总和。
选择最大深度处的任意一个节点 x。返回从节点 1 到节点 x 的路径上分配边权重使其总代价为奇数的方案数。
由于答案可能很大,返回其对 10^9 + 7 取模的结果。
注意:忽略从节点 1 到 x 路径上不存在的所有边。
示例 1:
输入:edges = [[1,2]]
输出:1
解释:
从节点 1 到节点 2 的路径包含一条边 (1 → 2)。
分配权重 1 使代价为奇数,而权重 2 使代价为偶数。因此,有效分配的数量是 1。
示例 2:
输入:edges = [[1,2],[1,3],[3,4],[3,5]]
输出:2
解释:
最大深度是 2,节点 4 和 5 在同一深度。可以选择任一节点进行处理。
例如,从节点 1 到节点 4 的路径包含两条边 (1 → 3 和 3 → 4)。
分配权重 (1,2) 或 (2,1) 会导致奇数代价。因此,有效分配的数量是 2。
约束条件:
- 2 <= n <= 10^5
- edges.length == n - 1
- edges[i] == [ui, vi]
- 1 <= ui, vi <= n
- edges 表示一个有效的树
解题思路
这道题的关键洞察在于理解奇偶性规律。
首先,我们需要找到树的最大深度。由于树以节点 1 为根,我们可以使用 DFS 从根节点开始遍历,计算每个节点的深度。
接下来分析权重分配的奇偶性:
- 每条边可以分配权重 1 或 2
- 路径总代价为奇数,意味着所有边权重之和为奇数
- 关键观察:权重为 2 的边不影响奇偶性(因为 2 是偶数)
- 只有权重为 1 的边会影响奇偶性
因此,要使路径总代价为奇数,我们需要在从根到最大深度节点的路径上选择奇数个边分配权重 1,其余边可以分配权重 1 或 2。
对于深度为 max_depth 的路径(包含 max_depth 条边):
- 选择奇数个位置放权重 1:C(max_depth, 1) + C(max_depth, 3) + …
- 每个选中位置必须是 1,其余位置可以是 1 或 2
- 数学上,从 n 个位置中选择奇数个的方案数是 2^(n-1)
因此答案就是 2^(max_depth-1) mod (10^9 + 7)。
代码实现
class Solution {
public:
int assignEdgeWeights(vector<vector<int>>& edges) {
const int MOD = 1000000007;
int n = edges.size() + 1;
// 建立邻接表
vector<vector<int>> adj(n + 1);
for (auto& edge : edges) {
adj[edge[0]].push_back(edge[1]);
adj[edge[1]].push_back(edge[0]);
}
// DFS 计算最大深度
int maxDepth = 0;
function<void(int, int, int)> dfs = [&](int node, int parent, int depth) {
maxDepth = max(maxDepth, depth);
for (int neighbor : adj[node]) {
if (neighbor != parent) {
dfs(neighbor, node, depth + 1);
}
}
};
dfs(1, -1, 0);
// 计算 2^(maxDepth-1) mod MOD
if (maxDepth == 0) return 0;
long long result = 1;
for (int i = 0; i < maxDepth - 1; i++) {
result = (result * 2) % MOD;
}
return result;
}
};
class Solution:
def assignEdgeWeights(self, edges: List[List[int]]) -> int:
MOD = 1000000007
n = len(edges) + 1
# 建立邻接表
adj = [[] for _ in range(n + 1)]
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
# DFS 计算最大深度
max_depth = 0
def dfs(node, parent, depth):
nonlocal max_depth
max_depth = max(max_depth, depth)
for neighbor in adj[node]:
if neighbor != parent:
dfs(neighbor, node, depth + 1)
dfs(1, -1, 0)
# 计算 2^(max_depth-1) mod MOD
if max_depth == 0:
return 0
return pow(2, max_depth - 1, MOD)
public class Solution {
public int AssignEdgeWeights(int[][] edges) {
const int MOD = 1000000007;
int n = edges.Length + 1;
// 建立邻接表
var adj = new List<int>[n + 1];
for (int i = 0; i <= n; i++) {
adj[i] = new List<int>();
}
foreach (var edge in edges) {
adj[edge[0]].Add(edge[1]);
adj[edge[1]].Add(edge[0]);
}
// DFS 计算最大深度
int maxDepth = 0;
void Dfs(int node, int parent, int depth) {
maxDepth = Math.Max(maxDepth, depth);
foreach (int neighbor in adj[node]) {
if (neighbor != parent) {
Dfs(neighbor, node, depth + 1);
}
}
}
Dfs(1, -1, 0);
// 计算 2^(maxDepth-1) mod MOD
if (maxDepth == 0) return 0;
long result = 1;
for (int i = 0; i < maxDepth - 1; i++) {
result = (result * 2) % MOD;
}
return (int)result;
}
}
var assignEdgeWeights = function(edges) {
const MOD = 1000000007;
const n = edges.length + 1;
// Build adjacency list
const adj = Array.from({length: n + 1}, () => []);
for (const [u, v] of edges) {
adj[u].push(v);
adj[v].push(u);
}
// Find maximum depth and path to deepest node
let maxDepth = 0;
let deepestNode = 1;
const parent = new Array(n + 1).fill(-1);
function dfs(node, par, depth) {
parent[node] = par;
if (depth > maxDepth) {
maxDepth = depth;
deepestNode = node;
}
for (const child of adj[node]) {
if (child !== par) {
dfs(child, node, depth + 1);
}
}
}
dfs(1, -1, 0);
// Find path from root to deepest node
const path = [];
let curr = deepestNode;
while (parent[curr] !== -1) {
path.push(curr);
curr = parent[curr];
}
path.push(1);
path.reverse();
// Number of edges in path
const pathEdges = path.length - 1;
if (pathEdges === 0) return 0;
// Count ways to make sum odd
// For k edges, we need odd number of edges with weight 1
// Total ways = 2^k, ways to make even sum = 2^(k-1), ways to make odd sum = 2^(k-1)
return Math.pow(2, pathEdges - 1) % MOD;
};
复杂度分析
| 复杂度类型 | 值 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(n) |
时间复杂度:DFS 遍历所有节点一次,时间复杂度为 O(n),计算幂次的时间复杂度为 O(max_depth),总体为 O(n)。 空间复杂度:邻接表存储需要 O(n) 空间,DFS 递归栈最深为树的高度,最坏情况下为 O(n)。