Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions problems/3312-sorted-gcd-pair-queries/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 3312. Sorted GCD Pair Queries

[LeetCode Link](https://leetcode.com/problems/sorted-gcd-pair-queries/)

Difficulty: Hard
Topics: Array, Hash Table, Math, Binary Search, Combinatorics, Counting, Number Theory, Prefix Sum
Acceptance Rate: 34.5%

## Hints

### Hint 1

There can be up to `n * (n - 1) / 2 ≈ 5 * 10^9` pairs, so you can never actually build the `gcdPairs` array. But notice that every GCD value is bounded: `1 <= gcd <= max(nums) <= 5 * 10^4`. That is a much smaller universe than the number of pairs. What can you count over that small range instead of enumerating pairs?

### Hint 2

Instead of asking "what is the gcd of each pair", flip it around: for each candidate value `g`, ask "how many pairs have gcd *exactly* `g`?" If you had that count for every `g`, the sorted `gcdPairs` array is just each `g` repeated that many times — and a prefix sum over `g` lets you answer any query with binary search. This is a classic **counting + number theory** setup.

### Hint 3

Counting pairs with gcd *exactly* `g` directly is hard, but counting pairs where both elements are *divisible by* `g` is easy: if `m` numbers are multiples of `g`, there are `C(m, 2) = m*(m-1)/2` such pairs. That count includes pairs whose gcd is `g`, `2g`, `3g`, ... So process `g` from large to small (or subtract as you go): `exact[g] = pairsDivisibleBy[g] - exact[2g] - exact[3g] - ...`. This inclusion–exclusion sieve is the crux. Then prefix-sum `exact[]` and binary-search each query.

## Approach

**Step 1 — Frequency table.** Let `M = max(nums)`. Build `cnt[v]` = how many elements of `nums` equal `v`, for `1 <= v <= M`.

**Step 2 — Count pairs divisible by each `g`.** For every `g` from `1` to `M`, sum `cnt[g] + cnt[2g] + cnt[3g] + ...` to get `m`, the number of array elements that are multiples of `g`. The number of pairs where *both* elements are multiples of `g` is `pairs[g] = m*(m-1)/2`. This harmonic-style double loop costs `O(M log M)`.

**Step 3 — Inclusion–exclusion to get exact gcd counts.** `pairs[g]` counts every pair whose gcd is a multiple of `g`. To isolate pairs with gcd *exactly* `g`, subtract the exact counts of all larger multiples:

```
exactCount[g] = pairs[g] - exactCount[2g] - exactCount[3g] - ...
```

Process `g` from `M` down to `1` so that `exactCount[2g], exactCount[3g], ...` are already finalized when you compute `exactCount[g]`. (You can also fold Steps 2 and 3 into one downward loop.)

**Step 4 — Prefix sums.** Build `prefix[g] = exactCount[1] + exactCount[2] + ... + exactCount[g]`. `prefix[g]` is the number of pairs whose gcd is `<= g`, i.e. how many entries of the conceptual sorted `gcdPairs` array are `<= g`.

**Step 5 — Answer queries.** For a query index `q` (0-based), the answer is the smallest `g` such that `prefix[g] > q`. Binary search over `g` in `[1, M]` finds it in `O(log M)`.

**Why it works.** The sorted `gcdPairs` array places all `exactCount[1]` ones first, then `exactCount[2]` twos, and so on. The prefix sum tells you exactly where each value's block ends, so locating the `q`-th element is a binary search on those block boundaries — no need to materialize the (possibly billions-long) array.

**Tiny example.** `nums = [2,3,4]`, `M = 4`, `cnt = [_,0,1,1,1]`.
- `pairs[1]`: multiples of 1 → 3 numbers → `C(3,2)=3`.
- `pairs[2]`: multiples of 2 → {2,4} → 2 numbers → `C(2,2)=1`.
- `pairs[3]`: {3} → 1 number → 0. `pairs[4]`: {4} → 0.
- Going down: `exact[4]=0`, `exact[3]=0`, `exact[2]=1-0=1`, `exact[1]=3-1=2`.
- `prefix = [_,2,3,3,3]`. Query 0 → smallest g with prefix>0 → g=1. Query 2 → smallest g with prefix>2 → g=2. Matches `[1,2,2]`.

## Complexity Analysis

Time Complexity: O(n + M log M + Q log M), where `n = len(nums)`, `M = max(nums)`, and `Q = len(queries)`. The `M log M` term comes from the harmonic sieve summing multiples; each query is a binary search.

Space Complexity: O(M) for the frequency, exact-count, and prefix arrays.

## Edge Cases

- **Only one distinct pair** (`n = 2`): `gcdPairs` has a single element; every query must return that element. Covered by the general prefix-sum logic.
- **Duplicate values** (e.g. `nums = [2,2]`): `gcd(2,2) = 2`, and `cnt[2] = 2` yields `C(2,2)=1` pair — duplicates must be counted, not deduplicated.
- **All numbers coprime**: most pairs collapse to gcd `1`, so `exactCount[1]` dominates; the prefix sum still resolves queries correctly.
- **Large query indices**: `queries[i]` can be up to `n*(n-1)/2 ≈ 5*10^9`, which overflows 32-bit integers. Use 64-bit (`int` on 64-bit Go, or `int64`) for query values, `m*(m-1)/2`, and prefix sums.
- **`max(nums)` sizing**: allocate arrays up to `M` inclusive; off-by-one on the upper bound drops the largest possible gcd.
79 changes: 79 additions & 0 deletions problems/3312-sorted-gcd-pair-queries/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
number: "3312"
frontend_id: "3312"
title: "Sorted GCD Pair Queries"
slug: "sorted-gcd-pair-queries"
difficulty: "Hard"
topics:
- "Array"
- "Hash Table"
- "Math"
- "Binary Search"
- "Combinatorics"
- "Counting"
- "Number Theory"
- "Prefix Sum"
acceptance_rate: 3451.3
is_premium: false
created_at: "2026-07-17T03:45:42.308459+00:00"
fetched_at: "2026-07-17T03:45:42.308459+00:00"
link: "https://leetcode.com/problems/sorted-gcd-pair-queries/"
date: "2026-07-17"
---

# 3312. Sorted GCD Pair Queries

You are given an integer array `nums` of length `n` and an integer array `queries`.

Let `gcdPairs` denote an array obtained by calculating the GCD of all possible pairs `(nums[i], nums[j])`, where `0 <= i < j < n`, and then sorting these values in **ascending** order.

For each query `queries[i]`, you need to find the element at index `queries[i]` in `gcdPairs`.

Return an integer array `answer`, where `answer[i]` is the value at `gcdPairs[queries[i]]` for each query.

The term `gcd(a, b)` denotes the **greatest common divisor** of `a` and `b`.



**Example 1:**

**Input:** nums = [2,3,4], queries = [0,2,2]

**Output:** [1,2,2]

**Explanation:**

`gcdPairs = [gcd(nums[0], nums[1]), gcd(nums[0], nums[2]), gcd(nums[1], nums[2])] = [1, 2, 1]`.

After sorting in ascending order, `gcdPairs = [1, 1, 2]`.

So, the answer is `[gcdPairs[queries[0]], gcdPairs[queries[1]], gcdPairs[queries[2]]] = [1, 2, 2]`.

**Example 2:**

**Input:** nums = [4,4,2,1], queries = [5,3,1,0]

**Output:** [4,2,1,1]

**Explanation:**

`gcdPairs` sorted in ascending order is `[1, 1, 1, 2, 2, 4]`.

**Example 3:**

**Input:** nums = [2,2], queries = [0,0]

**Output:** [2,2]

**Explanation:**

`gcdPairs = [2]`.



**Constraints:**

* `2 <= n == nums.length <= 105`
* `1 <= nums[i] <= 5 * 104`
* `1 <= queries.length <= 105`
* `0 <= queries[i] < n * (n - 1) / 2`
70 changes: 70 additions & 0 deletions problems/3312-sorted-gcd-pair-queries/solution_daily_20260717.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

// 3312. Sorted GCD Pair Queries
//
// Approach: counting + number-theory sieve + prefix sum + binary search.
//
// We never build the (up to ~5e9 long) gcdPairs array. Instead we count, for
// every value g in [1, M] where M = max(nums), how many pairs have gcd exactly g.
//
// 1. cnt[v] = frequency of value v in nums.
// 2. For each g, m = number of array elements divisible by g (sum cnt[g],
// cnt[2g], ...). Pairs with both elements divisible by g = m*(m-1)/2. This
// over-counts: it includes pairs whose gcd is 2g, 3g, ...
// 3. Sweep g from M down to 1 and subtract the already-finalized exact counts of
// larger multiples: exact[g] = pairsDivisibleBy(g) - exact[2g] - exact[3g] - ...
// 4. prefix[g] = number of pairs with gcd <= g (cumulative sum of exact).
// 5. Each query index q maps to the smallest g with prefix[g] > q (binary search).
//
// Time : O(n + M log M + Q log M)
// Space : O(M)
func gcdValues(nums []int, queries []int64) []int {
maxVal := 0
for _, v := range nums {
if v > maxVal {
maxVal = v
}
}

cnt := make([]int, maxVal+1)
for _, v := range nums {
cnt[v]++
}

// exact[g] will hold the number of pairs whose gcd is exactly g.
exact := make([]int64, maxVal+1)
for g := maxVal; g >= 1; g-- {
// m = count of array elements that are multiples of g.
var m int64
var over int64 // pairs already claimed by larger multiples of g
for mult := g; mult <= maxVal; mult += g {
m += int64(cnt[mult])
if mult > g {
over += exact[mult]
}
}
exact[g] = m*(m-1)/2 - over
}

// prefix[g] = number of pairs with gcd <= g. Length maxVal+1, prefix[0] = 0.
prefix := make([]int64, maxVal+1)
for g := 1; g <= maxVal; g++ {
prefix[g] = prefix[g-1] + exact[g]
}

answer := make([]int, len(queries))
for i, q := range queries {
// Smallest g in [1, maxVal] such that prefix[g] > q.
lo, hi := 1, maxVal
for lo < hi {
mid := (lo + hi) / 2
if prefix[mid] > q {
hi = mid
} else {
lo = mid + 1
}
}
answer[i] = lo
}
return answer
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"reflect"
"testing"
)

func TestSolution(t *testing.T) {
tests := []struct {
name string
nums []int
queries []int64
expected []int
}{
{
name: "example 1: [2,3,4] queries [0,2,2]",
nums: []int{2, 3, 4},
queries: []int64{0, 2, 2},
expected: []int{1, 2, 2},
},
{
name: "example 2: [4,4,2,1] queries [5,3,1,0]",
nums: []int{4, 4, 2, 1},
queries: []int64{5, 3, 1, 0},
expected: []int{4, 2, 1, 1},
},
{
name: "example 3: [2,2] queries [0,0]",
nums: []int{2, 2},
queries: []int64{0, 0},
expected: []int{2, 2},
},
{
name: "edge case: single pair coprime",
nums: []int{3, 5},
queries: []int64{0},
expected: []int{1},
},
{
name: "edge case: all duplicates yield same gcd",
nums: []int{6, 6, 6},
queries: []int64{0, 1, 2},
expected: []int{6, 6, 6},
},
{
name: "edge case: full sorted gcdPairs scanned in order",
// nums = [4,4,2,1] -> gcdPairs sorted = [1,1,1,2,2,4]
nums: []int{4, 4, 2, 1},
queries: []int64{0, 1, 2, 3, 4, 5},
expected: []int{1, 1, 1, 2, 2, 4},
},
{
name: "edge case: mixed values with larger gcds",
// nums = [10,15,20] -> gcd(10,15)=5, gcd(10,20)=10, gcd(15,20)=5
// sorted = [5,5,10]
nums: []int{10, 15, 20},
queries: []int64{0, 1, 2},
expected: []int{5, 5, 10},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := gcdValues(tt.nums, tt.queries)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("gcdValues(%v, %v) = %v, want %v", tt.nums, tt.queries, result, tt.expected)
}
})
}
}