Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions problems/3558-number-of-ways-to-assign-edge-weights-i/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 3558. Number of Ways to Assign Edge Weights I

[LeetCode Link](https://leetcode.com/problems/number-of-ways-to-assign-edge-weights-i/)

Difficulty: Medium
Topics: Math, Tree, Depth-First Search
Acceptance Rate: 59.4%

## Hints

### Hint 1

The problem mentions a tree and "maximum depth," but read the question carefully: you only ever care about the *path from the root to a deepest node*. That path is just a chain of edges. So the first job is simply to find how many edges are on that longest root-to-leaf path. Think about how you normally find the depth of a tree.

### Hint 2

Now focus on the counting. Each edge gets weight `1` or `2`. The cost of the path is the sum of those weights, and you want the sum to be **odd**. Notice that `2` is even and `1` is odd. So adding a weight-`2` edge never changes the parity of the running sum — only weight-`1` edges flip it. The parity of the total depends entirely on *how many edges you set to `1`*.

### Hint 3

So for a path with `k` edges, an assignment makes the cost odd exactly when an **odd number** of those edges are set to `1`. The number of subsets of a `k`-element set that have odd size is always `2^(k-1)` (it's exactly half of all `2^k` subsets, as long as `k >= 1`). Therefore the answer is `2^(k-1) mod (1e9 + 7)`, where `k` is the maximum depth (in edges). No DFS over weights is needed — just find `k` and do one fast exponentiation.

## Approach

**Step 1 — Find the maximum depth (in edges).**
Build an adjacency list from `edges`. The tree is rooted at node `1`. Run a BFS (or DFS) from node `1`, tracking the distance (number of edges) from the root. The largest distance reached is `k`, the length of the longest root-to-leaf path. BFS is convenient here because each level of the queue corresponds to one more edge of depth, so the number of levels traversed minus one is `k`.

**Step 2 — Count the odd-cost assignments.**
Along a path of `k` edges, every edge is independently `1` or `2`. The total cost is odd iff an odd number of edges carry weight `1`. The count of length-`k` binary choices with an odd number of "ones" is:

```
C(k,1) + C(k,3) + C(k,5) + ... = 2^(k-1)
```

This identity holds for every `k >= 1` (half of all `2^k` combinations have odd parity). Since the constraints guarantee `n >= 2`, there is always at least one edge, so `k >= 1` and the formula is well defined.

Compute `2^(k-1) mod (1e9 + 7)` using fast (binary) exponentiation to keep it efficient and avoid overflow.

**Why it works — a small example.**
Take `edges = [[1,2],[1,3],[3,4],[3,5]]`. BFS from `1`: depth 0 = {1}, depth 1 = {2,3}, depth 2 = {4,5}. The maximum depth is `k = 2`. The valid assignments on a 2-edge path are `(1,2)` and `(2,1)` — both have exactly one weight-`1` edge (odd count). That's `2^(2-1) = 2`, matching the expected output.

## Complexity Analysis

Time Complexity: O(n) — building the adjacency list and the BFS each visit every node and edge once; the fast exponentiation adds O(log k).
Space Complexity: O(n) — for the adjacency list and the BFS queue / visited bookkeeping.

## Edge Cases

- **Single edge (`n = 2`, `edges = [[1,2]]`):** Maximum depth `k = 1`, so the answer is `2^0 = 1`. Exactly one of the two weight choices (`1`) is odd. This is the smallest legal input given `n >= 2`.
- **All leaves at the same depth vs. an unbalanced tree:** Only the *maximum* depth matters; siblings or shorter branches are irrelevant. The BFS must track the deepest level, not the first leaf encountered.
- **Large depth (long chain):** With up to `10^5` nodes the path could have ~`10^5` edges, so `2^(k-1)` is astronomically large — you must reduce modulo `1e9 + 7` during the exponentiation, never compute the raw power.
- **Wide, shallow tree (star graph):** Many children of the root but depth `1`; the answer is still `1`. Confirms the count depends on depth, not node count.
74 changes: 74 additions & 0 deletions problems/3558-number-of-ways-to-assign-edge-weights-i/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
number: "3558"
frontend_id: "3558"
title: "Number of Ways to Assign Edge Weights I"
slug: "number-of-ways-to-assign-edge-weights-i"
difficulty: "Medium"
topics:
- "Math"
- "Tree"
- "Depth-First Search"
acceptance_rate: 5940.3
is_premium: false
created_at: "2026-06-11T05:07:20.705087+00:00"
fetched_at: "2026-06-11T05:07:20.705087+00:00"
link: "https://leetcode.com/problems/number-of-ways-to-assign-edge-weights-i/"
date: "2026-06-11"
---

# 3558. Number of Ways to Assign Edge Weights I

There is an undirected tree with `n` nodes labeled from 1 to `n`, rooted at node 1. The tree is represented by a 2D integer array `edges` of length `n - 1`, where `edges[i] = [ui, vi]` indicates that there is an edge between nodes `ui` and `vi`.

Initially, all edges have a weight of 0. You must assign each edge a weight of either **1** or **2**.

The **cost** of a path between any two nodes `u` and `v` is the total weight of all edges in the path connecting them.

Select any one node `x` at the **maximum** depth. Return the number of ways to assign edge weights in the path from node 1 to `x` such that its total cost is **odd**.

Since the answer may be large, return it **modulo** `109 + 7`.

**Note:** Ignore all edges **not** in the path from node 1 to `x`.



**Example 1:**

![](https://assets.leetcode.com/uploads/2025/03/23/screenshot-2025-03-24-at-060006.png)

**Input:** edges = [[1,2]]

**Output:** 1

**Explanation:**

* The path from Node 1 to Node 2 consists of one edge (`1 -> 2`).
* Assigning weight 1 makes the cost odd, while 2 makes it even. Thus, the number of valid assignments is 1.



**Example 2:**

![](https://assets.leetcode.com/uploads/2025/03/23/screenshot-2025-03-24-at-055820.png)

**Input:** edges = [[1,2],[1,3],[3,4],[3,5]]

**Output:** 2

**Explanation:**

* The maximum depth is 2, with nodes 4 and 5 at the same depth. Either node can be selected for processing.
* For example, the path from Node 1 to Node 4 consists of two edges (`1 -> 3` and `3 -> 4`).
* Assigning weights (1,2) or (2,1) results in an odd cost. Thus, the number of valid assignments is 2.





**Constraints:**

* `2 <= n <= 105`
* `edges.length == n - 1`
* `edges[i] == [ui, vi]`
* `1 <= ui, vi <= n`
* `edges` represents a valid tree.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package main

// 3558. Number of Ways to Assign Edge Weights I
//
// Approach:
// We only care about the path from the root (node 1) to a node at the maximum
// depth. Every edge on that path is assigned weight 1 (odd) or 2 (even). Since
// weight 2 never changes the parity of the running sum, the total cost is odd
// exactly when an odd number of edges are set to 1. For a path with k edges,
// the number of subsets of odd size is 2^(k-1). So the answer is
// 2^(maxDepth-1) mod (1e9+7), where maxDepth is the number of edges on the
// longest root-to-leaf path. We find maxDepth with a BFS and compute the power
// with fast (binary) exponentiation.

const mod = 1_000_000_007

func assignEdgeWeights(edges [][]int) int {
n := len(edges) + 1

// Build adjacency list (nodes are 1-indexed).
adj := make([][]int, n+1)
for _, e := range edges {
u, v := e[0], e[1]
adj[u] = append(adj[u], v)
adj[v] = append(adj[v], u)
}

// BFS from node 1 to find the maximum depth in edges.
visited := make([]bool, n+1)
queue := []int{1}
visited[1] = true
maxDepth := 0
for len(queue) > 0 {
next := make([]int, 0, len(queue))
for _, node := range queue {
for _, nb := range adj[node] {
if !visited[nb] {
visited[nb] = true
next = append(next, nb)
}
}
}
if len(next) > 0 {
maxDepth++
}
queue = next
}

// Number of odd-cost assignments on a path of maxDepth edges is 2^(maxDepth-1).
return powMod(2, maxDepth-1, mod)
}

// powMod computes base^exp mod m using binary exponentiation.
func powMod(base, exp, m int) int {
base %= m
result := 1
for exp > 0 {
if exp&1 == 1 {
result = result * base % m
}
base = base * base % m
exp >>= 1
}
return result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import "testing"

func TestAssignEdgeWeights(t *testing.T) {
tests := []struct {
name string
edges [][]int
expected int
}{
{
name: "example 1: single edge, depth 1",
edges: [][]int{{1, 2}},
expected: 1,
},
{
name: "example 2: max depth 2 with branching",
edges: [][]int{{1, 2}, {1, 3}, {3, 4}, {3, 5}},
expected: 2,
},
{
name: "edge case: star graph, all leaves at depth 1",
edges: [][]int{{1, 2}, {1, 3}, {1, 4}, {1, 5}},
expected: 1,
},
{
name: "edge case: straight chain of depth 5",
edges: [][]int{{1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}},
expected: 16, // 2^(5-1)
},
{
name: "edge case: unbalanced tree, deepest branch wins",
edges: [][]int{{1, 2}, {2, 3}, {1, 4}},
expected: 2, // max depth 2 -> 2^(2-1)
},
{
name: "edge case: edges given out of order",
edges: [][]int{{3, 4}, {1, 3}, {1, 2}},
expected: 2, // path 1->3->4 has depth 2
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := assignEdgeWeights(tt.edges)
if result != tt.expected {
t.Errorf("assignEdgeWeights(%v) = %d, want %d", tt.edges, result, tt.expected)
}
})
}
}