↓Skip to main content
  1. LeetCode/

LeetCode 2421: Number of Good Paths

·3 mins· ·
LeetCode Hard Tree Union-Find Graph Sorting
Wei Yi Chung
Author
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
Table of Contents

Meta Data
#

Difficulty: hard First Attempt: 2026-07-26

  • Submissions: Accepted (first try)

Approach
#

A good path has equal values at both ends and no value along the path larger than the ends.

Another way to see it: using only nodes with value <= v, which nodes of value v are connected? If a connected component holds k nodes of value v, every pair of them has a good path: k * (k - 1) / 2 paths.

So process values in increasing order:

  1. For value v, union each node of value v with neighbors of value <= v.
  2. Count the nodes of value v in each component and add k * (k - 1) / 2.
  3. Each single node is also a path, so the answer starts at n.

Because nodes are added in increasing order, unions only involve values <= v, so the path maximum never exceeds v.

My Solution
#

from collections import defaultdict

class UF:
    def __init__(self, n: int):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        rx, ry = self.find(x), self.find(y)
        if rx == ry:
            return
        if self.rank[rx] < self.rank[ry]:
            rx, ry = ry, rx
        self.parent[ry] = rx
        if self.rank[rx] == self.rank[ry]:
            self.rank[rx] += 1

class Solution:
    def numberOfGoodPaths(self, vals: List[int], edges: List[List[int]]) -> int:
        n = len(vals)
        adj = [[] for _ in range(n)]
        for u, v in edges:
            adj[u].append(v)
            adj[v].append(u)

        val_to_nodes = defaultdict(list)
        for i, val in enumerate(vals):
            val_to_nodes[val].append(i)

        uf = UF(n)
        res = n
        for val, nodes in sorted(val_to_nodes.items()):
            for node in nodes:                         # 1. union with neighbors <= val
                for nei in adj[node]:
                    if vals[nei] <= val:
                        uf.union(node, nei)

            group_count = defaultdict(int)             # 2. count value-val nodes per group
            for node in nodes:
                group_count[uf.find(node)] += 1

            for k in group_count.values():             # 3. pair them up
                res += k * (k - 1) // 2
        return res

The union I originally wrote had a small issue: equal ranks and a larger rank[rx] both went to else, and both incremented rank[rx]. The result was still correct; ranks just grew higher than needed. The version above increments only when the ranks are equal.

Complexity
#

Time O(n log n), dominated by sorting; Union-Find operations are nearly constant. Space O(n).

Mistakes to Avoid
#

  • Using vals[nei] < val when unioning: equal-value neighbors must be joined too, or two adjacent equal nodes are missed.
  • Counting once at the very end: count each value before moving to the next one, or larger values merge the groups too much.
  • Forgetting the n single-node paths.

Interview Explanation
#

A good path’s values never exceed its endpoints, so I process nodes in increasing value order. For value v, I union nodes of value v with neighbors of value at most v, so any path inside a component has maximum at most v. Then for each component with k nodes of value v I add k(k-1)/2, plus n single-node paths at the end. Sorting dominates, so time is O(n log n).

Related

LeetCode 3910: Count Connected Subgraphs with Even Node Sum
·3 mins
LeetCode Hard Bit-Manipulation Enumeration Graph Union-Find
LeetCode 3910 note: n is tiny, so enumerate subsets with a bitmask, check the even sum first, then check that the induced subgraph is connected
LeetCode 2603: Collect Coins in a Tree
·4 mins
LeetCode Hard Tree Graph Topological-Sort
LeetCode 2603 note: trim coin-free leaves, trim exactly two more layers, and walk each remaining edge twice
LeetCode 1782: Count Pairs Of Nodes
·3 mins
LeetCode Hard Two-Pointers Graph Sorting Hash-Table Counting
LeetCode 1782 note: count pairs with deg[a] + deg[b] > q using sorted degrees and converging pointers, then correct the over-count for pairs that share edges
LeetCode 3613: Minimize Maximum Component Cost
·2 mins
LeetCode Medium Union-Find Graph Sorting Binary-Search
LeetCode 3613 note: add edges from light to heavy; the weight when exactly k components remain is the answer, and 0 when n <= k
LeetCode 3608: Minimum Time for K Connected Components
·2 mins
LeetCode Medium Union-Find Graph Sorting Binary-Search
LeetCode 3608 note: add edges back from the largest time; the time of the first edge that drops the component count below k is the answer
LeetCode 1402: Reducing Dishes
·2 mins
LeetCode Hard Dynamic-Programming Greedy Sorting Array
LeetCode 1402 note: sort and take dishes from most satisfying down; each new dish goes first and shifts the chosen ones one slot later, adding the current chosen sum, so keep going while that sum is positive