Maximum Number of K-Divisible Components solution and explanation
9 more videos on this problem
Maximum Number of K-Divisible Components - Leetcode 2872 - PythonNeetCodeIO · 18:51 · 8K views
Maximum Number of K Divisible Components | Leetcode 2872Techdose · 16:55 · 6K views
Maximum Number of K-Divisible Components | Easiest Explanation | Dry Run | Leetcode 2872 | MIKcodestorywithMIK · 26:55 · 5K views
Maximum Number of K-Divisible Components - Leetcode 2872 - PythonNeetCodeIO · 18:51 · 5K views
2872. Maximum Number of K-Divisible Components | Leetcode Biweekly 114codingMohan · 21:41 · 1K views
Daily Leetcode #2068: Nov 28, 2025 - 2872. Maximum Number of K-Divisible ComponentsProgramming Live with Larry · 9:39 · 542 views
Daily Leetcode #1726: Dec 21, 2024 - 2872. Maximum Number of K-Divisible ComponentsProgramming Live with Larry · 12:30 · 476 views
2872. Maximum Number of K-Divisible Components (Leetcode Hard)Programming Live with Larry · 13:47 · 426 views
LeetCode 2872 Maximum Number of K Divisible Components | Tree | Hard | Asked in GoogleCode Harmony · 31:59 · 271 views
2872. Maximum Number of K-Divisible Components | Leetcode Daily - PythonLeetcode Daily · 9:23 · 178 views
The problem
There is an undirected tree with n nodes labeled from 0 to n - 1. You are given the integer n and a 2D integer array edges of length n - 1, where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the tree.
You are also given a 0-indexed integer array values of length n, where values[i] is the value associated with the ith node, and an integer k.
A valid split of the tree is obtained by removing any set of edges, possibly empty, from the tree such that the resulting components all have values that are divisible by k, where the value of a connected component is the sum of the values of its nodes.
Return the maximum number of components in any valid split.
Example 1:
Input: n = 5, edges = [[0,2],[1,2],[1,3],[2,4]], values = [1,8,1,4,4], k = 6 Output: 2 Explanation: We remove the edge connecting node 1 with 2. The resulting split is valid because: - The value of the component containing nodes 1 and 3 is values[1] + values[3] = 12. - The value of the component containing nodes 0, 2, and 4 is values[0] + values[2] + values[4] = 6. It can be shown that no other valid split has more than 2 connected components.
Example 2:
Input: n = 7, edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]], values = [3,0,6,1,5,2,1], k = 3 Output: 3 Explanation: We remove the edge connecting node 0 with 2, and the edge connecting node 0 with 1. The resulting split is valid because: - The value of the component containing node 0 is values[0] = 3. - The value of the component containing nodes 2, 5, and 6 is values[2] + values[5] + values[6] = 9. - The value of the component containing nodes 1, 3, and 4 is values[1] + values[3] + values[4] = 6. It can be shown that no other valid split has more than 3 connected components.
Constraints:
1 <= n <= 3 * 104edges.length == n - 1edges[i].length == 20 <= ai, bi < nvalues.length == n0 <= values[i] <= 1091 <= k <= 109- Sum of
valuesis divisible byk. - The input is generated such that
edgesrepresents a valid tree.
Approach overview
Problem Overview: You are given a tree with n nodes where each node has a value. The goal is to remove some edges so that every resulting connected component has a total sum divisible by k. Return the maximum number of such components you can form.
Approach 1: Brute Force Edge Removal + Component Sum Check (O(n^2) time, O(n) space)
One straightforward idea is to try removing edges and verify whether the resulting components have sums divisible by k. For each candidate edge removal, run a traversal such as DFS to compute the sum of nodes in each component. If both resulting sums are divisible by k, the cut is valid. However, recomputing component sums repeatedly leads to redundant traversals across the same nodes.
This approach relies heavily on repeated traversals of the tree, making it inefficient for large inputs. With up to n-1 edges and potentially full DFS traversals each time, the time complexity grows toward O(n^2). It mainly helps build intuition about why subtree sums are useful.
Approach 2: DFS with Subtree Sums (O(n) time, O(n) space)
The optimal approach uses a single postorder Depth-First Search traversal to compute subtree sums. Starting from any root (commonly node 0), recursively calculate the sum of each subtree. If the total sum of a subtree is divisible by k, you can treat that subtree as a separate component by conceptually cutting the edge connecting it to its parent.
The key insight: when a subtree sum is divisible by k, you increment the component count and return 0 upward instead of the actual sum. This prevents the parent subtree from including those nodes again. If the sum is not divisible by k, return the remainder sum so it contributes to the parent calculation.
Each node is visited exactly once and each edge is processed once, giving O(n) time complexity. The recursion stack and adjacency list together require O(n) space. This approach efficiently leverages subtree aggregation and modular arithmetic to maximize the number of valid components.
Recommended for interviews: Interviewers expect the DFS subtree-sum approach. It shows you understand postorder traversal on trees, modular arithmetic reasoning, and how to greedily cut valid subtrees during aggregation. Mentioning the brute force idea first demonstrates problem exploration, but implementing the linear-time DFS solution shows strong algorithmic maturity.
Approach 1: DFS with Subtree Sums
In this approach, we perform a Depth First Search (DFS) on the tree to calculate the sum of values for each subtree. We will transform the problem such that for each subtree, if its sum is divisible by k, we can potentially consider it as a valid component in our split. We keep track of these valid components as we traverse the tree.
def dfs(node, parent, values, k, adj):
subtree_sum = values[node]
components = 0
for neighbor in adj[node]:
if neighbor == parent:
continue
child_sum, child_components = dfs(neighbor, node, values, k, adj)
subtree_sum += child_sum
components += child_components
if subtree_sum % k == 0:
components += 1
subtree_sum = 0
return subtree_sum, components
def max_k_divisible_components(n, edges, values, k):
adj = {i: [] for i in range(n)}
for u, v in edges:
adj[u].append(v)
adj[v].append(u)
_, components = dfs(0, -1, values, k, adj)
return components - 1 # subtract 1 to account for the initial root componentimport java.util.*;
public class Solution {
private static int dfs(int node, int parent, int[] values, int k, Map<Integer, List<Integer>> adj) {
int subtree_sum = values[node];
int components = 0;
for (int neighbor : adj.get(node)) {
if (neighbor == parent) continue;
int[] result = dfs(neighbor, node, values, k, adj);
subtree_sum += result[0];
components += result[1];
}
if (subtree_sum % k == 0) {
components++;
subtree_sum = 0;
}
return new int[]{subtree_sum, components};
}
public static int maxKDivisibleComponents(int n, int[][] edges, int[] values, int k) {
Map<Integer, List<Integer>> adj = new HashMap<>();
for (int i = 0; i < n; i++) adj.put(i, new ArrayList<>());
for (int[] edge : edges) {
adj.get(edge[0]).add(edge[1]);
adj.get(edge[1]).add(edge[0]);
}
int[] result = dfs(0, -1, values, k, adj);
return result[1] - 1;
}
}#include <vector>
#include <unordered_map>
std::pair<int, int> dfs(int node, int parent, std::vector<int>& values, int k, std::unordered_map<int, std::vector<int>>& adj) {
int subtree_sum = values[node];
int components = 0;
for (int neighbor: adj[node]) {
if (neighbor == parent) continue;
auto [child_sum, child_components] = dfs(neighbor, node, values, k, adj);
subtree_sum += child_sum;
components += child_components;
}
if (subtree_sum % k == 0) {
components++;
subtree_sum = 0;
}
return {subtree_sum, components};
}
int max_k_divisible_components(int n, std::vector<std::vector<int>>& edges, std::vector<int>& values, int k) {
std::unordered_map<int, std::vector<int>> adj;
for (auto& edge : edges) {
adj[edge[0]].push_back(edge[1]);
adj[edge[1]].push_back(edge[0]);
}
auto [_, components] = dfs(0, -1, values, k, adj);
return components - 1;
}function dfs(node, parent, values, k, adj) {
let subtree_sum = values[node];
let components = 0;
for (let neighbor of adj.get(node)) {
if (neighbor === parent) continue;
const [child_sum, child_components] = dfs(neighbor, node, values, k, adj);
subtree_sum += child_sum;
components += child_components;
}
if (subtree_sum % k === 0) {
components++;
subtree_sum = 0;
}
return [subtree_sum, components];
}
function maxKDivisibleComponents(n, edges, values, k) {
const adj = new Map();
for (let i = 0; i < n; i++) adj.set(i, []);
for (const [u, v] of edges) {
adj.get(u).push(v);
adj.get(v).push(u);
}
const [, components] = dfs(0, -1, values, k, adj);
return components - 1;
}#include <stdio.h>
#include <stdlib.h>
static int components;
int dfs(int node, int parent, int* values, int k, int** adj, int* adjSize) {
int subtree_sum = values[node];
for (int i = 0; i < adjSize[node]; i++) {
int neighbor = adj[node][i];
if (neighbor == parent) continue;
subtree_sum += dfs(neighbor, node, values, k, adj, adjSize);
}
if (subtree_sum % k == 0) {
components++;
subtree_sum = 0;
}
return subtree_sum;
}
int maxKDivisibleComponents(int n, int** edges, int edgesSize, int* edgesColSize, int* values, int k) {
int** adj = malloc(n * sizeof(int*));
int* adjSize = calloc(n, sizeof(int));
for (int i = 0; i < n; i++) adj[i] = malloc(n * sizeof(int));
for (int i = 0; i < edgesSize; i++) {
int u = edges[i][0];
int v = edges[i][1];
adj[u][adjSize[u]++] = v;
adj[v][adjSize[v]++] = u;
}
components = 0;
dfs(0, -1, values, k, adj, adjSize);
for (int i = 0; i < n; i++) free(adj[i]);
free(adj);
free(adjSize);
return components - 1;
}using System;
using System.Collections.Generic;
public class Solution {
public int[] Dfs(int node, int parent, int[] values, int k, Dictionary<int, List<int>> adj) {
int subtree_sum = values[node];
int components = 0;
foreach (var neighbor in adj[node]) {
if (neighbor == parent) continue;
var result = Dfs(neighbor, node, values, k, adj);
subtree_sum += result[0];
components += result[1];
}
if (subtree_sum % k == 0) {
components++;
subtree_sum = 0;
}
return new int[] { subtree_sum, components };
}
public int MaxKDivisibleComponents(int n, int[][] edges, int[] values, int k) {
var adj = new Dictionary<int, List<int>>();
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]);
}
var result = Dfs(0, -1, values, k, adj);
return result[1] - 1;
}
}This solution uses a helper function dfs to recursively calculate the sum of values in each subtree and count the components with sums divisible by k. We adjust the subtree sum by checking and resetting it whenever it becomes divisible by k. Once the DFS completes, we return the total count of such components excluding the initial traversal from the root.
Time Complexity: O(n), where n is the number of nodes, as we traverse each node once.
Space Complexity: O(n) due to the recursion stack and adjacency list representation of the tree.
Approach 2: DFS
We note that the problem guarantees the sum of all node values in the entire tree is divisible by k. Therefore, if we remove a subtree whose sum of elements is divisible by k, the sum of node values in each of the remaining connected components must also be divisible by k.
Thus, we can use a depth-first search approach, starting from the root node to traverse the entire tree. For each node, we calculate the sum of all node values in its subtree. If this sum is divisible by k, we increment the answer by one.
The time complexity is O(n) and the space complexity is O(n), where n is the number of nodes in the tree.
function maxKDivisibleComponents(
n: number,
edges: number[][],
values: number[],
k: number,
): number {
const g: number[][] = Array.from({ length: n }, () => []);
for (const [a, b] of edges) {
g[a].push(b);
g[b].push(a);
}
let ans = 0;
const dfs = (i: number, fa: number): number => {
let s = values[i];
for (const j of g[i]) {
if (j !== fa) {
s += dfs(j, i);
}
}
if (s % k === 0) {
++ans;
}
return s;
};
dfs(0, -1);
return ans;
}func maxKDivisibleComponents(n int, edges [][]int, values []int, k int) (ans int) {
g := make([][]int, n)
for _, e := range edges {
a, b := e[0], e[1]
g[a] = append(g[a], b)
g[b] = append(g[b], a)
}
var dfs func(int, int) int
dfs = func(i, fa int) int {
s := values[i]
for _, j := range g[i] {
if j != fa {
s += dfs(j, i)
}
}
if s%k == 0 {
ans++
}
return s
}
dfs(0, -1)
return
}impl Solution {
pub fn max_k_divisible_components(n: i32, edges: Vec<Vec<i32>>, values: Vec<i32>, k: i32) -> i32 {
let n = n as usize;
let mut g = vec![vec![]; n];
for e in edges {
let a = e[0] as usize;
let b = e[1] as usize;
g[a].push(b);
g[b].push(a);
}
let mut ans = 0;
fn dfs(i: usize, fa: i32, g: &Vec<Vec<usize>>, values: &Vec<i32>, k: i32, ans: &mut i32) -> i64 {
let mut s = values[i] as i64;
for &j in &g[i] {
if j as i32 != fa {
s += dfs(j, i as i32, g, values, k, ans);
}
}
if s % k as i64 == 0 {
*ans += 1;
}
s
}
dfs(0, -1, &g, &values, k, &mut ans);
ans
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| DFS with Subtree Sums | Time Complexity: |
| DFS | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Brute Force Edge Removal + DFS | O(n^2) | O(n) | Conceptual understanding of how edge removal affects component sums |
| DFS with Subtree Sums | O(n) | O(n) | Optimal solution for large trees and typical interview expectations |
Frequently asked questions
Is Maximum Number of K-Divisible Components easy or hard?
Maximum Number of K-Divisible Components is classified as a Hard problem because it requires recognizing the subtree-sum pattern and combining DFS traversal with modular arithmetic. Once the insight about cutting divisible subtrees is understood, the implementation becomes relatively straightforward.
Maximum Number of K-Divisible Components Python/Java solution
Most implementations follow the same DFS structure across languages. Build the adjacency list, run a recursive DFS to compute subtree sums, and increment the component count whenever a subtree sum % k == 0. This logic translates directly to Python, Java, C++, JavaScript, and other languages with O(n) complexity.
How to solve Maximum Number of K-Divisible Components in O(n)?
Build an adjacency list for the tree and run a postorder DFS starting from the root. For each node, sum the values of its children’s subtrees. If the total subtree sum is divisible by k, increment the component count and return 0 upward; otherwise return the accumulated sum. Each node is processed once, giving O(n) time.
What is the best approach for Maximum Number of K-Divisible Components?
The best approach is a DFS with subtree sum calculation. Perform a postorder traversal of the tree and compute the sum of each subtree. If a subtree sum is divisible by k, count it as a separate component and return 0 to the parent. This greedy strategy ensures the maximum number of valid components and runs in O(n) time.
Is Maximum Number of K-Divisible Components asked at Google/Amazon/Meta?
Tree DFS problems involving subtree aggregation and modular conditions frequently appear in interviews at companies like Google, Amazon, and Meta. Variants of subtree sum, component partitioning, and tree DP are common patterns tested in senior-level coding rounds.
What data structure is used in Maximum Number of K-Divisible Components?
The core data structure is a tree represented using an adjacency list. The algorithm uses Depth-First Search (DFS) to traverse nodes and compute subtree sums while tracking divisible components.
What is the time complexity of Maximum Number of K-Divisible Components?
The optimal DFS-based solution runs in O(n) time because each node and edge in the tree is visited exactly once during traversal. Space complexity is O(n) due to the adjacency list representation and recursion stack used by DFS.
