Count Pairs Of Nodes
HardPrompt
You are given an undirected graph defined by an integer n, the number of nodes, and a 2D integer array edges, the edges in the graph, where edges[i] = [ui, vi] indicates that there is an undirected edge between ui and vi. You are also given an integer array queries.
Let incident(a, b) be defined as the number of edges that are connected to either node a or b.
The answer to the jth query is the number of pairs of nodes (a, b) that satisfy both of the following conditions:
a < bincident(a, b) > queries[j]
Return an array answers such that answers.length == queries.length and answers[j] is the answer of the jth query.
Note that there can be multiple edges between the same two nodes.
Example 1:
Input: n = 4, edges = [[1,2],[2,4],[1,3],[2,3],[2,1]], queries = [2,3]
Output: [6,5]
Explanation: The calculations for incident(a, b) are shown in the table above.
The answers for each of the queries are as follows:
- answers[0] = 6. All the pairs have an incident(a, b) value greater than 2.
- answers[1] = 5. All the pairs except (3, 4) have an incident(a, b) value greater than 3.Example 2:
Input: n = 5, edges = [[1,5],[1,5],[3,4],[2,5],[1,3],[5,1],[2,3],[2,5]], queries = [1,2,3,4,5]
Output: [10,10,9,8,6]
Constraints:
2 <= n <= 2 * 1041 <= edges.length <= 1051 <= ui, vi <= nui != vi1 <= queries.length <= 200 <= queries[j] < edges.length
Approaches
2 approaches with complexity analysis and trade-offs.
The most direct way to solve the problem is to check every possible pair of nodes (a, b) for each query. This involves calculating the incident(a, b) value for each pair and comparing it with the query value.
Algorithm
- First, pre-process the
edgesto calculate the degree of each node and the number of common edges between any two nodes. - We can use an array
degreesof sizen+1to store the degree of each node. - We can use a hash map, say
common_edges, to store the number of edges between any pair of nodes(u, v). The key can be a combined value likeu * (n+1L) + v(assumingu < v) to uniquely identify the pair. - This pre-computation takes
O(E)time, whereEis the number of edges. - Then, for each query
qinqueries:- We initialize a counter to zero.
- We iterate through all possible pairs of distinct nodes
(a, b)wherea < b. - For each pair, we calculate
incident(a, b) = degrees[a] + degrees[b] - common_edges.getOrDefault((a,b), 0). - If
incident(a, b) > q, we increment the counter. - After checking all pairs, the counter holds the answer for the query
q.
- The number of pairs is
n * (n-1) / 2, which isO(n^2). Since we do this for each query, this part takesO(Q * n^2).
Walkthrough
This approach begins by pre-calculating two key pieces of information: the degree of each node and the number of common edges for every pair of nodes. The degrees can be stored in an array, and the common edge counts in a hash map for efficient lookup. This setup phase takes time proportional to the number of edges, O(E).
Once the pre-computation is done, we process each query one by one. For a given query q, we iterate through every unique pair of nodes (a, b) with a < b. For each pair, we compute incident(a, b) using the pre-calculated degrees and common edge count. The formula is incident(a, b) = degree(a) + degree(b) - common_edges(a, b). If this value is greater than q, we increment a counter for the current query. This process is repeated for all O(n^2) pairs.
class Solution { public int[] countPairs(int n, int[][] edges, int[] queries) { int[] degrees = new int[n + 1]; Map<Long, Integer> commonEdges = new HashMap<>(); for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; degrees[u]++; degrees[v]++; if (u > v) { int temp = u; u = v; v = temp; } long key = (long) u * (n + 1) + v; commonEdges.put(key, commonEdges.getOrDefault(key, 0) + 1); } int[] answers = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int q = queries[i]; int count = 0; for (int a = 1; a <= n; a++) { for (int b = a + 1; b <= n; b++) { long key = (long) a * (n + 1) + b; int common = commonEdges.getOrDefault(key, 0); if (degrees[a] + degrees[b] - common > q) { count++; } } } answers[i] = count; } return answers; }}Complexity
Time
O(E + Q * n^2), where `E` is the number of edges, `Q` is the number of queries, and `n` is the number of nodes. The `O(E)` part is for pre-computation, and `O(Q * n^2)` is for processing the queries.
Space
O(n + E), where `n` is the number of nodes and `E` is the number of edges. This is for storing the `degrees` array and the `common_edges` map.
Trade-offs
Pros
Simple to understand and implement.
It correctly solves the problem for small inputs.
Cons
The time complexity is too high for the given constraints on
n, leading to a 'Time Limit Exceeded' error on most platforms.
The brute-force approach is too slow due to the O(n^2) loop for each query. We can optimize the counting by reformulating the condition incident(a, b) > q as degree(a) + degree(b) > q + common_edges(a, b). The common_edges term makes it dependent on the specific pair. A better strategy is to first count all pairs (a, b) satisfying the simpler condition degree(a) + degree(b) > q, and then subtract the pairs that were counted incorrectly due to the common_edges term.
Algorithm
- Pre-computation:
- Calculate the
degreesof all nodes by iterating throughedges. This takesO(E). - Store the count of
common_edgesfor each pair that has at least one edge. A hash map is suitable for this. This also takesO(E). - Create a copy of the
degreesarray (for nodes 1 to n) and sort it. Let's call itsorted_degrees. This takesO(n log n).
- Calculate the
- Query Processing: For each query
q:- Initial Count (Overestimation): Use a two-pointer technique on
sorted_degreesto count pairs(a, b)wheredegree(a) + degree(b) > q. This takesO(n).- Initialize
left = 0,right = n-1, andcount = 0. - While
left < right:- If
sorted_degrees[left] + sorted_degrees[right] > q, then all pairs(left, k)forkfromleft+1torightare valid. Addright - lefttocountand decrementright. - Otherwise, increment
left.
- If
- Initialize
- Correction: The initial count is an overestimation. We must subtract pairs
(u, v)that were counted but are invalid. These are pairs wheredegree(u) + degree(v) > qbutdegree(u) + degree(v) - common_edges(u, v) <= q.- Iterate through the
common_edgesmap. For each entry, check if it satisfies the correction condition. If so, decrement the total count for the query. - This step takes
O(E_unique), whereE_uniqueis the number of pairs with shared edges (E_unique <= E).
- Iterate through the
- Initial Count (Overestimation): Use a two-pointer technique on
- The final answer for the query is the corrected count.
Walkthrough
This approach significantly improves performance by avoiding the O(n^2) iteration. The key insight is to separate the counting process into two main parts: an initial, efficient overestimation, followed by a targeted correction.
First, we pre-calculate node degrees and common edge counts, similar to the brute-force method. Then, we create a sorted array of all node degrees. For each query q, we use this sorted array to find the number of pairs (a, b) where the sum of their degrees degree(a) + degree(b) is greater than q. This can be done in O(n) time using a two-pointer approach.
This initial count, however, doesn't account for the common_edges term. It overcounts by including pairs (a, b) that satisfy degree(a) + degree(b) > q but fail the actual condition degree(a) + degree(b) - common_edges(a, b) > q. To fix this, we perform a correction step. We iterate through only those pairs that have common edges (which we stored in our map). For each such pair, we check if it was wrongly included in our initial count. If degree(a) + degree(b) > q but degree(a) + degree(b) - common_edges(a, b) <= q, we decrement our count. This correction step's complexity depends on the number of unique pairs with edges, which is at most E.
class Solution { public int[] countPairs(int n, int[][] edges, int[] queries) { int[] degrees = new int[n + 1]; Map<Long, Integer> commonEdges = new HashMap<>(); for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; degrees[u]++; degrees[v]++; if (u > v) { int temp = u; u = v; v = temp; } long key = (long) u * (n + 1) + v; commonEdges.put(key, commonEdges.getOrDefault(key, 0) + 1); } int[] sortedDegrees = new int[n]; for (int i = 0; i < n; i++) { sortedDegrees[i] = degrees[i + 1]; } Arrays.sort(sortedDegrees); int[] answers = new int[queries.length]; for (int i = 0; i < queries.length; i++) { int q = queries[i]; int totalPairs = 0; int left = 0, right = n - 1; while (left < right) { if (sortedDegrees[left] + sortedDegrees[right] > q) { totalPairs += (right - left); right--; } else { left++; } } int corrections = 0; for (Map.Entry<Long, Integer> entry : commonEdges.entrySet()) { long key = entry.getKey(); int commonCount = entry.getValue(); int u = (int) (key / (n + 1)); int v = (int) (key % (n + 1)); int sumDeg = degrees[u] + degrees[v]; if (sumDeg > q && sumDeg - commonCount <= q) { corrections++; } } answers[i] = totalPairs - corrections; } return answers; }}Complexity
Time
O(E + n log n + Q * (n + E_unique)), where `E_unique` is the number of pairs with at least one edge between them (`E_unique <= E`). The `O(n log n)` is for sorting degrees. Each of the `Q` queries takes `O(n)` for the two-pointer scan and `O(E_unique)` for the correction.
Space
O(n + E), for storing degrees, a sorted copy of degrees, and the common edges map.
Trade-offs
Pros
Significantly more efficient than the brute-force approach.
The two-pointer technique for counting pairs based on degree sum is very fast.
Handles the problem constraints effectively.
Cons
The correction step iterates through all unique edges for every query, which could be slow if both the number of queries and edges are large.
Solutions
Solution
class Solution {public int[] countPairs(int n, int[][] edges, int[] queries) { int[] cnt = new int[n]; Map<Integer, Integer> g = new HashMap<>(); for (var e : edges) { int a = e[0] - 1, b = e[1] - 1; ++cnt[a]; ++cnt[b]; int k = Math.min(a, b) * n + Math.max(a, b); g.merge(k, 1, Integer : : sum); } int[] s = cnt.clone(); Arrays.sort(s); int[] ans = new int[queries.length]; for (int i = 0; i < queries.length; ++i) { int t = queries[i]; for (int j = 0; j < n; ++j) { int x = s[j]; int k = search(s, t - x, j + 1); ans[i] += n - k; } for (var e : g.entrySet()) { int a = e.getKey() / n, b = e.getKey() % n; int v = e.getValue(); if (cnt[a] + cnt[b] > t && cnt[a] + cnt[b] - v <= t) { --ans[i]; } } } return ans; }private int search(int[] arr, int x, int i) { int left = i, right = arr.length; while (left < right) { int mid = (left + right) >> 1; if (arr[mid] > x) { right = mid; } else { left = mid + 1; } } return left; }}class Solution {public: vector<int> countPairs(int n, vector<vector<int>> &edges, vector<int> &queries) { vector<int> cnt(n); unordered_map<int, int> g; for (auto &e : edges) { int a = e[0] - 1, b = e[1] - 1; ++cnt[a]; ++cnt[b]; int k = min(a, b) * n + max(a, b); ++g[k]; } vector<int> s = cnt; sort(s.begin(), s.end()); vector<int> ans(queries.size()); for (int i = 0; i < queries.size(); ++i) { int t = queries[i]; for (int j = 0; j < n; ++j) { int x = s[j]; int k = upper_bound(s.begin() + j + 1, s.end(), t - x) - s.begin(); ans[i] += n - k; } for (auto &[k, v] : g) { int a = k / n, b = k % n; if (cnt[a] + cnt[b] > t && cnt[a] + cnt[b] - v <= t) { --ans[i]; } } } return ans; }};class Solution: def countPairs(self, n: int, edges: List[List[int]], queries: List[int]) -> List[int]: cnt = [0] * n g = defaultdict(int) for a, b in edges: a, b = a - 1, b - 1 a, b = min(a, b), max(a, b) cnt[a] += 1 cnt[b] += 1 g[(a, b)] += 1 s = sorted(cnt) ans = [0] * len(queries) for i, t in enumerate(queries): for j, x in enumerate(s): k = bisect_right(s, t - x, lo=j + 1) ans[i] += n - k for (a, b), v in g . items(): if cnt[a] + cnt[b] > t and cnt[a] + cnt[b] - v <= t: ans[i] -= 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.