Number of Different Subsequences GCDs: Enumerate Candidate GCDs
LeetCode 1819. Number of Different Subsequences GCDs asks you to find how many distinct GCD values are possible across all non-empty subsequences of a given array of positive integers. A subsequence can skip elements but must preserve relative order, and the GCD of a sequence is the largest integer that evenly divides every element.
For example, given nums = [6, 10, 3], the non-empty subsequences and their GCDs are:
[6]with GCD 6,[10]with GCD 10,[3]with GCD 3[6, 10]with GCD 2,[6, 3]with GCD 3,[10, 3]with GCD 1[6, 10, 3]with GCD 1
The distinct GCD values are {1, 2, 3, 6, 10}, so the answer is 5.
The approach
There are up to 2^n non-empty subsequences, so brute-forcing every one is not feasible when n can be as large as 200,000. Instead, you can flip the question around: for each candidate GCD value g from 1 to max(nums), ask whether g is actually achievable by some subsequence.
To check if g is achievable, iterate through all multiples of g (that is, g, 2g, 3g, and so on up to max(nums)). For each multiple that exists in the array, fold it into a running GCD. If the running GCD ever reaches exactly g, then some subsequence of the array elements that are multiples of g has GCD equal to g, and you count it.
Why does this work? Any subsequence whose GCD is g must consist entirely of multiples of g. So you only need to consider the array elements that are multiples of g. If the GCD of all those elements is exactly g, then g is achievable. If their GCD is some value larger than g (which would itself be a multiple of g), then no subset of them can produce a GCD of exactly g.
The total work across all candidates is proportional to max_val * (1/1 + 1/2 + ... + 1/max_val), which is O(max_val * log(max_val)). This harmonic series bound makes the algorithm efficient even when max_val is up to 200,000.
The code
from math import gcd
from typing import List
class Solution:
def countDifferentSubsequenceGCDs(self, nums: List[int]) -> int:
num_set = set(nums)
max_val = max(nums)
count = 0
for g in range(1, max_val + 1):
current_gcd = 0
for multiple in range(g, max_val + 1, g):
if multiple in num_set:
current_gcd = gcd(current_gcd, multiple)
if current_gcd == g:
break
if current_gcd == g:
count += 1
return count
Here is what happens step by step:
- Build a set from
numsso you can check membership in O(1). - Find
max_val, the largest number in the array. - For each candidate
gfrom 1 tomax_val, walk through every multiple ofgup tomax_val. - Whenever a multiple is found in the set, update the running GCD with
gcd(current_gcd, multiple). - If the running GCD equals
g, break early and increment the count. - Return the total count of achievable GCD values.
Visual walkthrough
Here is the candidate-by-candidate process for nums = [6, 10, 3]:
Step 1: Store array elements in a set for O(1) lookup
We need to quickly check whether a particular number exists in nums. A set gives us constant-time membership tests.
Step 2: Candidate g = 1. Check multiples 1, 2, 3, ..., 10
We find 3 in the set (running GCD = 3), then 6 (GCD(3,6) = 3), then 10 (GCD(3,10) = 1). The running GCD reached 1, so g = 1 is achievable.
Step 3: Candidate g = 2. Check multiples 2, 4, 6, 8, 10
We find 6 in the set (running GCD = 6), then 10 (GCD(6,10) = 2). The running GCD reached 2, so g = 2 is achievable.
Step 4: Candidate g = 3. Check multiples 3, 6, 9
We find 3 in the set (running GCD = 3). The running GCD already equals g, so we can stop early. g = 3 is achievable.
Step 5: Candidates g = 4 and g = 5 both fail
For g = 4, the only multiple in the set is 8? No, none of 4, 8 are present. For g = 5, the multiple 10 is present, but GCD(10) = 10 which is not 5. Neither candidate is achievable.
Step 6: Candidates g = 6 through g = 10
g = 6 succeeds because 6 is in the array (GCD = 6). g = 7, 8, 9 have no multiples present. g = 10 succeeds because 10 is in the array (GCD = 10). Final count = 5.
Notice how the early-break optimization saves work. For g = 3, the algorithm finds 3 in the set immediately and stops, since GCD(3) = 3 = g.
Complexity analysis
| Approach | Time | Space |
|---|---|---|
| Brute force (all subsequences) | O(2^n * n) | O(n) |
| Candidate enumeration | O(max_val * log(max_val)) | O(n) |
The candidate enumeration approach iterates over every value g from 1 to max_val, and for each g it checks max_val / g multiples. The total number of multiple-checks across all candidates is max_val * (1 + 1/2 + 1/3 + ... + 1/max_val), which is the harmonic series summing to roughly max_val * ln(max_val). The space is O(n) for the set storing the original array elements.
Building blocks
GCD computation
The greatest common divisor of two integers can be computed in O(log(min(a, b))) time using the Euclidean algorithm. Python's math.gcd handles this efficiently, and the key property we rely on is that gcd(0, x) = x, which lets us initialize the running GCD to 0 and fold in elements one at a time.
Harmonic series enumeration
When you need to check all multiples of every integer from 1 to N, the total work is bounded by the harmonic series: N/1 + N/2 + N/3 + ... + N/N = N * H(N), where H(N) is approximately ln(N). This same pattern appears in the Sieve of Eratosthenes and many other number-theoretic algorithms.
Edge cases
- Single element: If
numshas just one element like[7], the only subsequence is[7]with GCD 7, so the answer is 1. - All elements identical: If
nums = [5, 5, 5], every non-empty subsequence has GCD 5, so the answer is 1. - Consecutive integers: If
nums = [1, 2, 3, ..., n], then every integer from 1 to n is achievable as a GCD, giving an answer of n. - Two coprime elements: If
nums = [6, 35], the subsequences give GCDs 6, 35, andgcd(6, 35) = 1, so the answer is 3.
From understanding to recall
This problem combines two building blocks: GCD computation and harmonic series enumeration. The key insight, iterating over candidate values rather than over subsequences, is a pattern that appears whenever brute-forcing all subsets is too expensive but the answer space is bounded. Practicing this problem alongside sieve-based problems like Count Primes will reinforce the "enumerate multiples" technique. Revisiting it after a few days will help cement the connection between the harmonic series bound and the inner loop structure.
Related posts
- Count Primes - Sieve-like enumeration of multiples
- Ugly Number II - Number theory with multiple factors
- Burst Balloons - Optimizing over subsequences