Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1617. Count Subtrees With Max Distance Between Cities - Leetcode Solution

Problem Description

You are given an integer n representing n cities numbered from 1 to n, and an array edges where each edges[i] = [u, v] indicates that there is a bidirectional road connecting city u and city v. The cities and roads form a connected, undirected tree.

For every integer d in the range 1 to n-1, you need to count how many connected subtrees of the original tree have a maximum distance between any two cities equal to d. The maximum distance between any two cities in a subtree is the length (number of edges) of the longest path in that subtree.

Return an array ans of length n-1 where ans[d-1] is the number of connected subtrees with max distance d.

  • Each subtree must be connected and consist of at least two cities.
  • Each pair of cities in a subtree must be connected via the subtree's roads.
  • Do not reuse elements; every subset is considered only once.

Thought Process

At first glance, the problem seems to require examining all possible subsets of cities and checking which of them form connected subtrees. For each such subtree, we must compute the diameter (the largest distance between any two nodes).

The brute-force approach would be to generate all possible subsets of the n cities, check if each subset forms a connected tree, and then compute the diameter for each. However, since there are 2^n possible subsets, this is computationally infeasible for n up to 15.

We need an optimization. Since n is small (≤ 15), we can use bitmasking to represent subsets. For each subset, we can use BFS or DFS to check connectivity and calculate the diameter efficiently. The key insight is that the total number of subsets is manageable, and we can prune subsets that are not connected early.

Solution Approach

  • Represent Subsets with Bitmask: Each subset of cities is represented by a bitmask of length n. If the ith bit is set, city i is included in the subset.
  • Iterate Over All Subsets: For each bitmask from 1 to 2^n - 1, consider only those with at least two cities (i.e., at least two bits set).
  • Check Connectivity: For each subset, use BFS or DFS to check if all included cities are connected. If not, skip this subset.
  • Compute Diameter: For connected subsets, compute the diameter (the longest shortest path between any two nodes in the subset). This can be done via two BFS passes: start from any node in the subset, find the farthest node, then BFS again from that node to find the maximum distance.
  • Count by Diameter: For each diameter d, increment ans[d-1].
  • Return the Result: After processing all subsets, return the ans array.

We use adjacency lists for efficient graph traversal, and bitmasking to enumerate all possible city subsets. BFS is used both for connectivity checks and for computing the diameter within each subset.

Example Walkthrough

Consider n = 4 and edges = [[1,2],[2,3],[2,4]]. The tree is:

      1
      |
      2
     / \
    3   4
  
  1. Enumerate all subsets with at least two cities (there are 11 such subsets).
  2. For each subset, check if it's connected. For example, subset {1,2,3} is connected, but {1,3,4} is not.
  3. For connected subsets, compute the diameter:
    • {1,2}: diameter = 1
    • {2,3}: diameter = 1
    • {2,4}: diameter = 1
    • {1,2,3}: diameter = 2
    • {1,2,4}: diameter = 2
    • {2,3,4}: diameter = 2
    • {1,2,3,4}: diameter = 2
  4. Count the number of subtrees for each possible diameter. For this example, ans = [3, 4, 0] (since n-1=3).

Time and Space Complexity

  • Brute-force Approach: O(2^n \cdot n^2) — For each subset (of which there are 2^n), we check connectivity and compute the diameter (each can be done in O(n^2)).
  • Optimized Approach: The same as above, but we use bitmasking and efficient BFS/DFS. For n ≤ 15, this is acceptable (~32,000 subsets).
  • Space Complexity: O(n^2) for the adjacency list and O(2^n) for bitmask enumeration.

The time complexity is exponential in n, but the constraints allow it.

Summary

The problem is solved by representing all possible city subsets as bitmasks, checking which ones are connected subtrees, and computing the diameter for each using BFS. The approach leverages the small value of n to perform an exhaustive search efficiently. The key insight is that for small trees, bitmask enumeration and BFS are practical and effective.

Code Implementation

class Solution:
    def countSubgraphsForEachDiameter(self, n, edges):
        from collections import deque, defaultdict

        # Build adjacency list
        graph = [[] for _ in range(n)]
        for u, v in edges:
            graph[u-1].append(v-1)
            graph[v-1].append(u-1)
        
        ans = [0] * (n-1)

        # For all subsets of nodes (bitmask representation)
        for mask in range(1, 1 << n):
            # Only consider subsets with at least 2 nodes
            if bin(mask).count('1') < 2:
                continue

            # Find first node in subset
            nodes = [i for i in range(n) if (mask & (1 << i))]
            start = nodes[0]

            # Check connectivity via BFS
            visited = set()
            queue = deque([start])
            visited.add(start)
            while queue:
                curr = queue.popleft()
                for nei in graph[curr]:
                    if (mask & (1 << nei)) and nei not in visited:
                        visited.add(nei)
                        queue.append(nei)
            if len(visited) != len(nodes):
                continue  # Not connected

            # Compute diameter in the subset
            def bfs(u):
                seen = set([u])
                queue = deque([(u, 0)])
                farthest = (u, 0)
                while queue:
                    node, dist = queue.popleft()
                    if dist > farthest[1]:
                        farthest = (node, dist)
                    for v in graph[node]:
                        if (mask & (1 << v)) and v not in seen:
                            seen.add(v)
                            queue.append((v, dist+1))
                return farthest

            far_node, _ = bfs(start)
            _, diameter = bfs(far_node)
            if diameter > 0:
                ans[diameter-1] += 1

        return ans
      
class Solution {
public:
    vector<int> countSubgraphsForEachDiameter(int n, vector<vector<int>>& edges) {
        vector<vector<int>> graph(n);
        for (auto& e : edges) {
            int u = e[0] - 1, v = e[1] - 1;
            graph[u].push_back(v);
            graph[v].push_back(u);
        }
        vector<int> ans(n-1, 0);

        int total = 1 << n;
        for (int mask = 1; mask < total; ++mask) {
            if (__builtin_popcount(mask) < 2) continue;
            vector<int> nodes;
            for (int i = 0; i < n; ++i)
                if (mask & (1 << i)) nodes.push_back(i);

            // Check connectivity via BFS
            queue<int> q;
            vector<bool> visited(n, false);
            q.push(nodes[0]);
            visited[nodes[0]] = true;
            int count = 1;
            while (!q.empty()) {
                int curr = q.front(); q.pop();
                for (int nei : graph[curr]) {
                    if ((mask & (1 << nei)) && !visited[nei]) {
                        visited[nei] = true;
                        q.push(nei);
                        ++count;
                    }
                }
            }
            if (count != nodes.size()) continue;

            // Compute diameter in the subset
            auto bfs = [&](int u) {
                queue<pair<int,int>> q;
                vector<bool> seen(n, false);
                q.push({u,0});
                seen[u] = true;
                pair<int,int> farthest = {u,0};
                while (!q.empty()) {
                    auto [node, dist] = q.front(); q.pop();
                    if (dist > farthest.second) farthest = {node, dist};
                    for (int v : graph[node]) {
                        if ((mask & (1 << v)) && !seen[v]) {
                            seen[v] = true;
                            q.push({v, dist+1});
                        }
                    }
                }
                return farthest;
            };

            auto far = bfs(nodes[0]);
            auto far2 = bfs(far.first);
            int diameter = far2.second;
            if (diameter > 0) ans[diameter-1]++;
        }
        return ans;
    }
};
      
class Solution {
    public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) graph.add(new ArrayList<>());
        for (int[] e : edges) {
            graph.get(e[0]-1).add(e[1]-1);
            graph.get(e[1]-1).add(e[0]-1);
        }
        int[] ans = new int[n-1];

        int total = 1 << n;
        for (int mask = 1; mask < total; mask++) {
            if (Integer.bitCount(mask) < 2) continue;
            List<Integer> nodes = new ArrayList<>();
            for (int i = 0; i < n; i++)
                if ((mask & (1 << i)) != 0) nodes.add(i);

            // Check connectivity via BFS
            Queue<Integer> q = new LinkedList<>();
            boolean[] visited = new boolean[n];
            q.offer(nodes.get(0));
            visited[nodes.get(0)] = true;
            int count = 1;
            while (!q.isEmpty()) {
                int curr = q.poll();
                for (int nei : graph.get(curr)) {
                    if (((mask & (1 << nei)) != 0) && !visited[nei]) {
                        visited[nei] = true;
                        q.offer(nei);
                        count++;
                    }
                }
            }
            if (count != nodes.size()) continue;

            // Compute diameter in the subset
            class Pair {
                int node, dist;
                Pair(int n, int d) { node = n; dist = d; }
            }
            java.util.function.Function<Integer, Pair> bfs = (u) -> {
                Queue<Pair> q2 = new LinkedList<>();
                boolean[] seen = new boolean[n];
                q2.offer(new Pair(u, 0));
                seen[u] = true;
                Pair farthest = new Pair(u, 0);
                while (!q2.isEmpty()) {
                    Pair p = q2.poll();
                    if (p.dist > farthest.dist) farthest = p;
                    for (int v : graph.get(p.node)) {
                        if (((mask & (1 << v)) != 0) && !seen[v]) {
                            seen[v] = true;
                            q2.offer(new Pair(v, p.dist+1));
                        }
                    }
                }
                return farthest;
            };
            Pair far = bfs.apply(nodes.get(0));
            Pair far2 = bfs.apply(far.node);
            int diameter = far2.dist;
            if (diameter > 0) ans[diameter-1]++;
        }
        return ans;
    }
}
      
var countSubgraphsForEachDiameter = function(n, edges) {
    const graph = Array.from({length: n}, () => []);
    for (const [u, v] of edges) {
        graph[u-1].push(v-1);
        graph[v-1].push(u-1);
    }
    const ans = Array(n-1).fill(0);

    const total = 1 << n;
    for (let mask = 1; mask < total; ++mask) {
        if (countBits(mask) < 2) continue;
        const nodes = [];
        for (let i = 0; i < n; ++i)
            if (mask & (1 << i)) nodes.push(i);

        // Check connectivity via BFS
        const visited = new Array(n).fill(false);
        const queue = [nodes[0]];
        visited[nodes[0]] = true;
        let count = 1;
        while (queue.length) {
            const curr = queue.shift();
            for (const nei of graph[curr]) {
                if ((mask & (1 << nei)) && !visited[nei]) {
                    visited[nei] = true;
                    queue.push(nei);
                    count++;
                }
            }
        }
        if (count !== nodes.length) continue;

        // Compute diameter in the subset
        function bfs(u) {
            const seen = new Array(n).fill(false);
            const queue = [[u, 0]];
            seen[u] = true;
            let farthest = [u, 0];
            while (queue.length) {
                const [node, dist] = queue.shift();
                if (dist > farthest[1]) farthest = [node, dist];
                for (const v of graph[node]) {
                    if ((mask & (1 << v)) && !seen[v]) {
                        seen[v] = true;
                        queue.push([v, dist+1]);
                    }
                }
            }
            return farthest;
        }

        const far = bfs(nodes[0]);
        const far2 = bfs(far[0]);
        const diameter = far2[1];
        if (diameter > 0) ans[diameter-1]++;
    }
    return ans;

    function countBits(x) {
        let cnt = 0;
        while (x) {
            cnt += x & 1;
            x >>= 1;
        }
        return cnt;
    }
};