Daily DSA: Dynamic Connected Components with Max Weight (Medium)
Problem Description
You are given $n$ nodes numbered from $0$ to $n - 1$, where each node $i$ has an associated integer weight specified by weights[i]. Initially, there are no edges between any of the nodes, so each node forms its own isolated connected component.
You are given a list of operations called queries, where each query is represented as a 3-element array [type, u, v]:
- Type 1 (
[1, u, v]): Add an undirected edge between nodeuand nodev. If they are already in the same connected component, this operation has no effect on component structure. - Type 2 (
[2, u, -1]): Query the maximum weight among all nodes in the connected component containing nodeu. The third valuevis set to-1and should be ignored.
Return an array of integers containing the answers to all Type 2 queries in the order they appear.
Examples
Example 1:
Input:
n = 5
weights = [10, 20, 15, 30, 5]
queries = [
[2, 0, -1],
[1, 0, 1],
[2, 0, -1],
[1, 1, 2],
[2, 2, -1],
[1, 3, 4],
[2, 4, -1]
]
Output: [10, 20, 20, 30]
Explanation:
[2, 0, -1]: Component containing node 0 has nodes{0}with max weight10.[1, 0, 1]: Add edge(0, 1). Component becomes{0, 1}with weights[10, 20].[2, 0, -1]: Component containing node 0 is{0, 1}. Max weight is20.[1, 1, 2]: Add edge(1, 2). Component becomes{0, 1, 2}with weights[10, 20, 15].[2, 2, -1]: Component containing node 2 is{0, 1, 2}. Max weight is20.[1, 3, 4]: Add edge(3, 4). Component becomes{3, 4}with weights[30, 5].[2, 4, -1]: Component containing node 4 is{3, 4}. Max weight is30.
Example 2:
Input:
n = 3
weights = [50, 10, 40]
queries = [
[1, 0, 1],
[1, 1, 2],
[2, 1, -1]
]
Output: [50]
Explanation:
After merging all 3 nodes into a single component {0, 1, 2}, the weights present are [50, 10, 40]. Querying node 1 yields the maximum weight 50.
Constraints
- $1 \le n \le 10^5$
weights.length$== n$- $1 \le \text{weights}[i] \le 10^9$
- $1 \le \text{queries.length} \le 10^5$
- Each query is formatted as
[type, u, v]wheretype$\in {1, 2}$, $0 \le u, v < n$. - For
type == 2, $v = -1$.
Approach
This problem can be efficiently solved using the Disjoint Set Union (DSU) (or Union-Find) data structure:
- DSU Initialization:
- Maintain a
parentarray whereparent[i] = iinitially. - Maintain a
max_weightarray initialized withmax_weight[i] = weights[i]for each representative root. - Optionally use union by rank/size for optimal time complexity.
- Maintain a
- Path Compression:
- Implement the
find(u)operation with path compression to achieve near $O(1)$ amortized lookup time.
- Implement the
- Union Operation (
[1, u, v]):- Find the roots
root_uandroot_vofuandv. - If they belong to different components, merge them by setting
parent[root_v] = root_u. - Update
max_weight[root_u] = max(max_weight[root_u], max_weight[root_v]).
- Find the roots
- Query Operation (
[2, u, -1]):- Find the root of node
u:root_u = find(u). - The maximum weight for this component is directly stored at
max_weight[root_u].
- Find the root of node
Complexity Analysis:
- Time Complexity: $O(n + q \cdot \alpha(n))$, where $q$ is the number of queries and $\alpha$ is the Inverse Ackermann function. This is effectively $O(n + q)$ in practice.
- Space Complexity: $O(n)$ auxiliary space to store the DSU parent pointers and maximum weights.
C++ Source Code
#include <vector>
#include <algorithm>
#include <numeric>
class DSU {
private:
std::vector<int> parent;
std::vector<int> max_weight;
std::vector<int> rank;
public:
DSU(int n, const std::vector<int>& weights) {
parent.resize(n);
std::iota(parent.begin(), parent.end(), 0);
max_weight = weights;
rank.assign(n, 0);
}
int find(int i) {
if (parent[i] == i)
return i;
return parent[i] = find(parent[i]);
}
void unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
if (rank[root_i] < rank[root_j]) {
std::swap(root_i, root_j);
}
parent[root_j] = root_i;
max_weight[root_i] = std::max(max_weight[root_i], max_weight[root_j]);
if (rank[root_i] == rank[root_j]) {
rank[root_i]++;
}
}
}
int getMaxWeight(int i) {
int root = find(i);
return max_weight[root];
}
};
class Solution {
public:
std::vector<int> processQueries(int n, std::vector<int>& weights, std::vector<std::vector<int>>& queries) {
DSU dsu(n, weights);
std::vector<int> result;
for (const auto& query : queries) {
int type = query[0];
int u = query[1];
int v = query[2];
if (type == 1) {
dsu.unite(u, v);
} else if (type == 2) {
result.push_back(dsu.getMaxWeight(u));
}
}
return result;
}
};