3312. Sorted GCD Pair Queries
Problem Description
You have an integer array nums
of length n
and an integer array queries
.
The problem asks you to:
-
Generate all possible GCD pairs: Calculate the GCD (Greatest Common Divisor) for every possible pair
(nums[i], nums[j])
where0 <= i < j < n
. This means you need to find the GCD for all combinations of two different elements from the array. -
Create a sorted GCD array: Take all these GCD values and sort them in ascending order. This sorted array is called
gcdPairs
. -
Answer queries: For each value
queries[i]
, return the element at indexqueries[i]
in thegcdPairs
array.
The function should return an array answer
where answer[i]
contains the value at position queries[i]
in the sorted gcdPairs
array.
Example to illustrate:
- If
nums = [2, 4, 6]
, the pairs would be:(2,4)
,(2,6)
,(4,6)
- The GCDs would be:
gcd(2,4) = 2
,gcd(2,6) = 2
,gcd(4,6) = 2
- So
gcdPairs = [2, 2, 2]
after sorting - If
queries = [0, 1, 2]
, the answer would be[2, 2, 2]
The key challenge is efficiently computing and storing the GCD values for all pairs, especially when dealing with large arrays, since the number of pairs grows quadratically with the size of the input array.
Intuition
The naive approach would be to compute the GCD for all pairs, sort them, and answer queries directly. However, with n
elements, we'd have n*(n-1)/2
pairs, which could be extremely large and inefficient.
The key insight is to think about the problem from a different angle: instead of computing individual GCDs, we can count how many pairs have each possible GCD value.
Why is this helpful? Consider that:
- The GCD of any pair must be a divisor of both numbers in the pair
- The GCD values are limited by the maximum value in the array (at most
max(nums)
different values) - If we know the count of pairs for each GCD value, we can build the sorted
gcdPairs
array conceptually without storing all individual values
Here's the clever observation: if we want to count pairs with GCD equal to g
, we need to:
- First, count all pairs where both numbers are divisible by
g
(these pairs have GCD that's at leastg
) - Then subtract pairs whose GCD is actually
2g, 3g, 4g, ...
(multiples ofg
)
For example, if we want pairs with GCD = 2:
- Count all pairs where both numbers are divisible by 2
- Subtract pairs with GCD = 4, 6, 8, ... (which we've already computed)
This suggests a bottom-up approach: start from the largest possible GCD and work downwards. For each value i
:
- Count how many numbers in
nums
are multiples ofi
. If there arev
such numbers, they can formv*(v-1)/2
pairs - These pairs have GCD that's a multiple of
i
, but some might have larger GCDs - Subtract the counts of pairs with GCD equal to multiples of
i
(which we've already calculated)
Once we have counts for each GCD value, we can use prefix sums to quickly determine which GCD value corresponds to each query index, using binary search to find the answer efficiently.
This transforms a potentially quadratic problem into one that's linear in the range of values, making it much more efficient.
Learn more about Math, Binary Search, Combinatorics and Prefix Sum patterns.
Solution Approach
Let's walk through the implementation step by step:
Step 1: Initialize data structures
- Find
mx = max(nums)
- the maximum value in the array - Create
cnt = Counter(nums)
to store the frequency of each number - Initialize
cnt_g = [0] * (mx + 1)
to store the count of pairs with each GCD value
Step 2: Calculate GCD counts (traverse from large to small)
We iterate i
from mx
down to 1. For each value i
:
-
Count multiples of
i
:v = 0 for j in range(i, mx + 1, i): v += cnt[j]
This counts how many numbers in
nums
are multiples ofi
(i.e.,i, 2i, 3i, ...
) -
Calculate pairs with GCD being a multiple of
i
:cnt_g[i] += v * (v - 1) // 2
If there are
v
numbers that are multiples ofi
, they can formv*(v-1)/2
pairs, all having GCD that's at leasti
. -
Subtract over-counted pairs:
for j in range(i, mx + 1, i): cnt_g[i] -= cnt_g[j]
We need to subtract pairs whose GCD is actually
2i, 3i, 4i, ...
(not exactlyi
). Since we're iterating from large to small, these values have already been computed.
Step 3: Build prefix sum array
s = list(accumulate(cnt_g))
The prefix sum array s[i]
tells us how many pairs have GCD less than or equal to i
. This essentially gives us the sorted gcdPairs
array in compressed form.
Step 4: Answer queries using binary search
return [bisect_right(s, q) for q in queries]
For each query q
, we use bisect_right
to find the smallest index i
where s[i] > q
. This index i
is the GCD value at position q
in the conceptual sorted gcdPairs
array.
Example walkthrough:
If nums = [4, 6, 8]
and mx = 8
:
- For
i = 8
: One number (8) is divisible by 8, so 0 pairs - For
i = 4
: Two numbers (4, 8) divisible by 4, giving 1 pair with GCD ≥ 4 - For
i = 2
: All three numbers divisible by 2, giving 3 pairs, minus 1 pair with GCD=4, equals 2 pairs with GCD=2 - For
i = 1
: Would count all pairs minus those already counted
The prefix sum array then allows us to map query indices to their corresponding GCD values efficiently.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [4, 6, 8]
and queries = [0, 1, 2]
.
Step 1: Setup
- Maximum value:
mx = 8
- Frequency counter:
cnt = {4: 1, 6: 1, 8: 1}
- Initialize
cnt_g = [0, 0, 0, 0, 0, 0, 0, 0, 0]
(indices 0 to 8)
Step 2: Calculate GCD counts (from largest to smallest)
For i = 8:
- Multiples of 8 in nums: just 8 →
v = 1
- Pairs with GCD ≥ 8:
1 * (1-1) / 2 = 0
- No multiples to subtract
cnt_g[8] = 0
For i = 7:
- No multiples of 7 in nums →
v = 0
cnt_g[7] = 0
For i = 6:
- Multiples of 6: just 6 →
v = 1
- Pairs with GCD ≥ 6:
1 * 0 / 2 = 0
cnt_g[6] = 0
For i = 5:
- No multiples of 5 →
cnt_g[5] = 0
For i = 4:
- Multiples of 4: 4 and 8 →
v = 2
- Pairs with GCD ≥ 4:
2 * 1 / 2 = 1
- Subtract cnt_g[8] = 0 (since 8 is a multiple of 4)
cnt_g[4] = 1 - 0 = 1
- This represents the pair (4, 8) with GCD = 4
For i = 3:
- Multiples of 3: just 6 →
v = 1
cnt_g[3] = 0
For i = 2:
- Multiples of 2: 4, 6, and 8 →
v = 3
- Pairs with GCD ≥ 2:
3 * 2 / 2 = 3
- Subtract: cnt_g[4] = 1, cnt_g[6] = 0, cnt_g[8] = 0
cnt_g[2] = 3 - 1 = 2
- This represents pairs (4, 6) and (6, 8), both with GCD = 2
For i = 1:
- All numbers are multiples of 1 →
v = 3
- Pairs:
3 * 2 / 2 = 3
- Subtract all other GCDs: 0 + 2 + 0 + 1 + 0 + 0 + 0 + 0 = 3
cnt_g[1] = 3 - 3 = 0
Step 3: Build prefix sum array
cnt_g = [0, 0, 2, 0, 1, 0, 0, 0, 0]
- Prefix sums:
s = [0, 0, 2, 2, 3, 3, 3, 3, 3]
This means:
- Indices 0-1 in gcdPairs have GCD = 2 (2 pairs)
- Index 2 in gcdPairs has GCD = 4 (1 pair)
Step 4: Answer queries
queries[0] = 0
: bisect_right(s, 0) = 2 → answer is 2queries[1] = 1
: bisect_right(s, 1) = 2 → answer is 2queries[2] = 2
: bisect_right(s, 2) = 4 → answer is 4
Verification: The actual pairs and their GCDs:
- gcd(4, 6) = 2
- gcd(4, 8) = 4
- gcd(6, 8) = 2
Sorted gcdPairs = [2, 2, 4]
So queries[0] = 2, queries[1] = 2, queries[2] = 4 ✓
The algorithm efficiently computes these values without explicitly generating all pairs, using the counting technique to handle large inputs effectively.
Solution Implementation
1from typing import List
2from collections import Counter
3from itertools import accumulate
4from bisect import bisect_right
5
6class Solution:
7 def gcdValues(self, nums: List[int], queries: List[int]) -> List[int]:
8 # Find the maximum value in nums to determine the range
9 max_value = max(nums)
10
11 # Count frequency of each number in nums
12 frequency_count = Counter(nums)
13
14 # Array to store count of pairs with GCD equal to index
15 gcd_pair_count = [0] * (max_value + 1)
16
17 # Calculate GCD counts from largest to smallest using inclusion-exclusion principle
18 for gcd in range(max_value, 0, -1):
19 # Count numbers that are multiples of current gcd
20 multiples_count = 0
21
22 # Iterate through all multiples of current gcd
23 for multiple in range(gcd, max_value + 1, gcd):
24 multiples_count += frequency_count[multiple]
25 # Subtract pairs already counted with larger GCDs (inclusion-exclusion)
26 gcd_pair_count[gcd] -= gcd_pair_count[multiple]
27
28 # Add number of pairs that can be formed from multiples
29 # Using combination formula: C(n, 2) = n * (n - 1) / 2
30 gcd_pair_count[gcd] += multiples_count * (multiples_count - 1) // 2
31
32 # Create prefix sum array for binary search
33 prefix_sum = list(accumulate(gcd_pair_count))
34
35 # For each query, find the GCD value using binary search on prefix sum
36 return [bisect_right(prefix_sum, query) for query in queries]
37
1class Solution {
2 public int[] gcdValues(int[] nums, long[] queries) {
3 // Find the maximum value in the array
4 int maxValue = Arrays.stream(nums).max().getAsInt();
5
6 // Count frequency of each number
7 int[] frequency = new int[maxValue + 1];
8 for (int num : nums) {
9 frequency[num]++;
10 }
11
12 // Calculate the count of pairs with GCD equal to each value
13 long[] gcdPairCount = new long[maxValue + 1];
14
15 // Process from largest to smallest to apply inclusion-exclusion principle
16 for (int gcd = maxValue; gcd > 0; gcd--) {
17 // Count elements that are multiples of current gcd
18 int multiplesCount = 0;
19 for (int multiple = gcd; multiple <= maxValue; multiple += gcd) {
20 multiplesCount += frequency[multiple];
21 // Subtract pairs already counted with larger GCDs (inclusion-exclusion)
22 gcdPairCount[gcd] -= gcdPairCount[multiple];
23 }
24 // Add all possible pairs from elements that are multiples of gcd
25 // C(multiplesCount, 2) = multiplesCount * (multiplesCount - 1) / 2
26 gcdPairCount[gcd] += (long) multiplesCount * (multiplesCount - 1) / 2;
27 }
28
29 // Convert to prefix sum array for binary search
30 for (int i = 2; i <= maxValue; i++) {
31 gcdPairCount[i] += gcdPairCount[i - 1];
32 }
33
34 // Process each query using binary search
35 int queryCount = queries.length;
36 int[] result = new int[queryCount];
37 for (int i = 0; i < queryCount; i++) {
38 result[i] = search(gcdPairCount, queries[i]);
39 }
40
41 return result;
42 }
43
44 /**
45 * Binary search to find the smallest index where cumulative count > target
46 * @param cumulativeCounts prefix sum array of GCD pair counts
47 * @param target the query value to search for
48 * @return the GCD value corresponding to the target-th pair
49 */
50 private int search(long[] cumulativeCounts, long target) {
51 int arrayLength = cumulativeCounts.length;
52 int left = 0;
53 int right = arrayLength;
54
55 // Binary search for the first position where cumulativeCounts[mid] > target
56 while (left < right) {
57 int mid = left + (right - left) / 2;
58 if (cumulativeCounts[mid] > target) {
59 right = mid;
60 } else {
61 left = mid + 1;
62 }
63 }
64
65 return left;
66 }
67}
68
1class Solution {
2public:
3 vector<int> gcdValues(vector<int>& nums, vector<long long>& queries) {
4 // Find the maximum value in the input array
5 int maxValue = ranges::max(nums);
6
7 // Count frequency of each number in the input array
8 vector<int> frequencyCount(maxValue + 1);
9 for (int number : nums) {
10 ++frequencyCount[number];
11 }
12
13 // Calculate the number of pairs with each possible GCD value
14 // gcdPairCount[i] will store the number of pairs with GCD exactly equal to i
15 vector<long long> gcdPairCount(maxValue + 1);
16
17 // Process GCD values from largest to smallest
18 for (int gcd = maxValue; gcd >= 1; --gcd) {
19 // Count total elements that are multiples of current gcd
20 long long multiplesCount = 0;
21
22 // Iterate through all multiples of current gcd
23 for (int multiple = gcd; multiple <= maxValue; multiple += gcd) {
24 multiplesCount += frequencyCount[multiple];
25
26 // Subtract pairs that have GCD as a multiple of current gcd
27 // (to get pairs with GCD exactly equal to current gcd)
28 gcdPairCount[gcd] -= gcdPairCount[multiple];
29 }
30
31 // Add all possible pairs from elements that are multiples of current gcd
32 // Formula: C(n, 2) = n * (n - 1) / 2
33 gcdPairCount[gcd] += multiplesCount * (multiplesCount - 1) / 2;
34 }
35
36 // Convert to cumulative sum for binary search
37 // gcdPairCount[i] now represents the number of pairs with GCD <= i
38 for (int i = 2; i <= maxValue; ++i) {
39 gcdPairCount[i] += gcdPairCount[i - 1];
40 }
41
42 // Process each query using binary search
43 vector<int> result;
44 for (const auto& query : queries) {
45 // Find the smallest GCD value where cumulative count > query
46 // This gives us the (query + 1)-th smallest GCD in sorted order
47 int gcdValue = upper_bound(gcdPairCount.begin(), gcdPairCount.end(), query) - gcdPairCount.begin();
48 result.push_back(gcdValue);
49 }
50
51 return result;
52 }
53};
54
1function gcdValues(nums: number[], queries: number[]): number[] {
2 // Find the maximum value in the input array
3 const maxValue = Math.max(...nums);
4
5 // Count frequency of each number in the input array
6 const frequencyCount: number[] = new Array(maxValue + 1).fill(0);
7 for (const number of nums) {
8 frequencyCount[number]++;
9 }
10
11 // Calculate the number of pairs with each possible GCD value
12 // gcdPairCount[i] will store the number of pairs with GCD exactly equal to i
13 const gcdPairCount: number[] = new Array(maxValue + 1).fill(0);
14
15 // Process GCD values from largest to smallest
16 for (let gcd = maxValue; gcd >= 1; gcd--) {
17 // Count total elements that are multiples of current gcd
18 let multiplesCount = 0;
19
20 // Iterate through all multiples of current gcd
21 for (let multiple = gcd; multiple <= maxValue; multiple += gcd) {
22 multiplesCount += frequencyCount[multiple];
23
24 // Subtract pairs that have GCD as a multiple of current gcd
25 // (to get pairs with GCD exactly equal to current gcd)
26 gcdPairCount[gcd] -= gcdPairCount[multiple];
27 }
28
29 // Add all possible pairs from elements that are multiples of current gcd
30 // Formula: C(n, 2) = n * (n - 1) / 2
31 gcdPairCount[gcd] += Math.floor(multiplesCount * (multiplesCount - 1) / 2);
32 }
33
34 // Convert to cumulative sum for binary search
35 // gcdPairCount[i] now represents the number of pairs with GCD <= i
36 for (let i = 2; i <= maxValue; i++) {
37 gcdPairCount[i] += gcdPairCount[i - 1];
38 }
39
40 // Process each query using binary search
41 const result: number[] = [];
42 for (const query of queries) {
43 // Find the smallest GCD value where cumulative count > query
44 // This gives us the (query + 1)-th smallest GCD in sorted order
45 const gcdValue = upperBound(gcdPairCount, query);
46 result.push(gcdValue);
47 }
48
49 return result;
50}
51
52// Helper function to find the first index where array[index] > target
53function upperBound(array: number[], target: number): number {
54 let left = 0;
55 let right = array.length;
56
57 while (left < right) {
58 const mid = Math.floor((left + right) / 2);
59 if (array[mid] <= target) {
60 left = mid + 1;
61 } else {
62 right = mid;
63 }
64 }
65
66 return left;
67}
68
Time and Space Complexity
Time Complexity: O(n + M * log M + q * log M)
- Creating the Counter from nums takes
O(n)
time - The nested loop iterates through all divisors:
- The outer loop runs from
mx
down to 1, which isO(M)
iterations - For each value
i
, the inner loop iterates through multiples ofi
up tomx
, which takesO(M/i)
time - The total time for all iterations is
O(M/1 + M/2 + M/3 + ... + M/M) = O(M * log M)
(harmonic series)
- The outer loop runs from
- Building the prefix sum array with accumulate takes
O(M)
time - For each of the
q
queries, binary search on the prefix sum array takesO(log M)
time - Total:
O(n + M * log M + q * log M)
which simplifies toO(n + (M + q) * log M)
Space Complexity: O(M)
- The Counter object stores at most
n
elements but uses keys up to valueM
, effectivelyO(M)
in worst case - The
cnt_g
array has sizeM + 1
, which isO(M)
- The prefix sum array
s
also has sizeM + 1
, which isO(M)
- Total space complexity is
O(M)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Order of Subtraction in Inclusion-Exclusion
The Pitfall:
A common mistake is subtracting the over-counted pairs BEFORE calculating the total pairs with GCD being a multiple of i
. This leads to incorrect results because you're subtracting from a value that hasn't been computed yet.
Incorrect Implementation:
# WRONG: Subtracting before calculating total pairs
for gcd in range(max_value, 0, -1):
multiples_count = 0
# Subtract first (WRONG!)
for multiple in range(gcd, max_value + 1, gcd):
gcd_pair_count[gcd] -= gcd_pair_count[multiple]
# Then calculate pairs
for multiple in range(gcd, max_value + 1, gcd):
multiples_count += frequency_count[multiple]
gcd_pair_count[gcd] += multiples_count * (multiples_count - 1) // 2
Correct Implementation:
# CORRECT: Calculate total pairs first, then subtract over-counted
for gcd in range(max_value, 0, -1):
multiples_count = 0
# First, count all multiples
for multiple in range(gcd, max_value + 1, gcd):
multiples_count += frequency_count[multiple]
# Calculate total pairs with GCD being a multiple of current value
gcd_pair_count[gcd] = multiples_count * (multiples_count - 1) // 2
# Then subtract pairs with GCD that is a proper multiple (2*gcd, 3*gcd, etc.)
for multiple in range(2 * gcd, max_value + 1, gcd):
gcd_pair_count[gcd] -= gcd_pair_count[multiple]
2. Starting Subtraction from Wrong Multiple
The Pitfall:
Another subtle error is starting the subtraction loop from gcd
instead of 2 * gcd
. This causes the algorithm to subtract gcd_pair_count[gcd]
from itself, which zeros out the value incorrectly.
Incorrect Implementation:
# WRONG: Starting from gcd itself
for multiple in range(gcd, max_value + 1, gcd): # Starts at gcd
gcd_pair_count[gcd] -= gcd_pair_count[multiple]
Correct Implementation:
# CORRECT: Starting from 2*gcd to avoid self-subtraction
for multiple in range(2 * gcd, max_value + 1, gcd): # Starts at 2*gcd
gcd_pair_count[gcd] -= gcd_pair_count[multiple]
3. Off-by-One Error with Query Indices
The Pitfall:
Using bisect_left
instead of bisect_right
(or vice versa) without proper adjustment can lead to incorrect query answers, especially at boundary cases.
Why it matters:
bisect_right(s, q)
returns the smallest index where all values to the left are≤ q
- This directly gives us the GCD value at position
q
in our conceptual sorted array - Using
bisect_left
would require additional logic to handle edge cases
Example to illustrate the difference:
If prefix_sum = [0, 2, 5, 8]
and query = 2
:
bisect_right([0, 2, 5, 8], 2)
returns2
(correct GCD value)bisect_left([0, 2, 5, 8], 2)
returns1
(would need adjustment)
The three-steps of Depth First Search are:
- Identify states;
- Draw the state-space tree;
- DFS on the state-space tree.
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Want a Structured Path to Master System Design Too? Don’t Miss This!