1819. Number of Different Subsequences GCDs
Problem Description
You have an array nums
containing positive integers. Your task is to find how many different GCD values can be obtained from all possible non-empty subsequences of this array.
The GCD (Greatest Common Divisor) of a sequence is the largest integer that divides all numbers in that sequence evenly. For instance, the GCD of [4, 6, 16]
is 2
because 2 is the largest number that divides 4, 6, and 16 without remainder.
A subsequence is formed by selecting some (or all) elements from the original array while maintaining their relative order. Elements don't need to be consecutive. For example, from the array [1, 2, 1, 2, 4, 1, 5, 10]
, we can form the subsequence [2, 5, 10]
by selecting the 2nd, 7th, and 8th elements.
The problem asks you to:
- Consider all possible non-empty subsequences of
nums
- Calculate the GCD for each subsequence
- Count how many distinct GCD values exist
For example, if nums = [6, 10, 3]
, some possible subsequences and their GCDs would be:
[6]
→ GCD = 6[10]
→ GCD = 10[3]
→ GCD = 3[6, 3]
→ GCD = 3[6, 10]
→ GCD = 2[10, 3]
→ GCD = 1[6, 10, 3]
→ GCD = 1
The distinct GCD values are: 1, 2, 3, 6, 10, giving us an answer of 5.
Intuition
Instead of generating all possible subsequences (which would be exponentially many), we can think about this problem from a different angle: what are all the possible GCD values that could exist?
The key insight is that any GCD of a subsequence must be between 1 and the maximum value in the array. Why? Because the GCD of any set of numbers cannot exceed the smallest number in that set, and the largest possible GCD would be when we pick just the maximum element by itself.
So we can flip our approach: instead of finding subsequences and computing their GCDs, we can check each potential GCD value x
from 1 to max(nums)
and determine if there exists some subsequence with GCD equal to x
.
How do we check if a GCD value x
is achievable? Here's the mathematical insight: if x
is the GCD of some subsequence, then all elements in that subsequence must be multiples of x
. This is because x
divides all elements in the subsequence evenly.
Therefore, for each candidate GCD value x
, we only need to look at elements in nums
that are multiples of x
. We can do this efficiently by checking values x, 2x, 3x, 4x, ...
up to the maximum value in the array.
For the multiples of x
that exist in nums
, we compute their GCD. If this GCD equals x
, then we've found a valid subsequence with GCD x
. The reason this works is that we're essentially finding the "tightest" common divisor among all multiples of x
in the array. If their GCD is exactly x
, it means we can form a subsequence with that GCD.
This approach is much more efficient than brute force because we're iterating through potential GCD values (at most max(nums)
values) and for each one, checking its multiples, rather than generating all 2^n
possible subsequences.
Learn more about Math patterns.
Solution Approach
The implementation follows our intuition by enumerating each possible GCD value and checking if it's achievable:
-
Find the maximum value: First, we find
mx = max(nums)
to establish the upper bound for possible GCD values. -
Create a set for O(1) lookup: We convert
nums
to a setvis = set(nums)
to enable constant-time checks for whether a number exists in the original array. -
Enumerate potential GCD values: We iterate through each potential GCD value
x
from 1 tomx
. -
Check multiples of x: For each candidate GCD
x
, we check all its multiplesy
that could exist in the array:- We iterate
y
fromx
tomx
in steps ofx
(i.e.,x, 2x, 3x, ...
) - This efficiently generates all multiples of
x
up to the maximum value
- We iterate
-
Calculate GCD of found multiples:
- We maintain a running GCD variable
g
, initially set to 0 - For each multiple
y
that exists invis
, we updateg = gcd(g, y)
- The property
gcd(0, n) = n
for any numbern
helps us start the GCD calculation
- We maintain a running GCD variable
-
Check if the GCD equals x:
- If at any point
g == x
, we know thatx
is achievable as a GCD of some subsequence - We increment our answer counter and break early (optimization)
- Breaking early is valid because once
g == x
, adding more multiples won't change the fact that the GCD isx
- If at any point
-
Return the count: The final answer is the total count of distinct GCD values found.
The time complexity is O(mx * log(mx))
where mx
is the maximum value in nums
. For each potential GCD value x
, we check approximately mx/x
multiples, and the total work across all values is bounded by the harmonic series. The gcd
operation takes O(log(mx))
time.
The space complexity is O(n)
for storing the set of numbers from the input array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [6, 10, 3]
.
Step 1: Setup
- Find maximum:
mx = 10
- Create set:
vis = {3, 6, 10}
- Initialize answer counter:
ans = 0
Step 2: Check each potential GCD from 1 to 10
x = 1:
- Check multiples: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
- Found in array: 3, 6, 10
- Calculate GCD:
g = gcd(0, 3) = 3
, theng = gcd(3, 6) = 3
, theng = gcd(3, 10) = 1
- Since
g = 1 == x
, GCD of 1 is achievable!ans = 1
x = 2:
- Check multiples: 2, 4, 6, 8, 10
- Found in array: 6, 10
- Calculate GCD:
g = gcd(0, 6) = 6
, theng = gcd(6, 10) = 2
- Since
g = 2 == x
, GCD of 2 is achievable!ans = 2
x = 3:
- Check multiples: 3, 6, 9
- Found in array: 3, 6
- Calculate GCD:
g = gcd(0, 3) = 3
, theng = gcd(3, 6) = 3
- Since
g = 3 == x
, GCD of 3 is achievable!ans = 3
x = 4:
- Check multiples: 4, 8
- Found in array: none
- No multiples found, skip
x = 5:
- Check multiples: 5, 10
- Found in array: 10
- Calculate GCD:
g = gcd(0, 10) = 10
- Since
g = 10 ≠ 5
, GCD of 5 is not achievable
x = 6:
- Check multiples: 6
- Found in array: 6
- Calculate GCD:
g = gcd(0, 6) = 6
- Since
g = 6 == x
, GCD of 6 is achievable!ans = 4
x = 7:
- Check multiples: 7
- Found in array: none
- No multiples found, skip
x = 8:
- Check multiples: 8
- Found in array: none
- No multiples found, skip
x = 9:
- Check multiples: 9
- Found in array: none
- No multiples found, skip
x = 10:
- Check multiples: 10
- Found in array: 10
- Calculate GCD:
g = gcd(0, 10) = 10
- Since
g = 10 == x
, GCD of 10 is achievable!ans = 5
Final Answer: 5
The distinct achievable GCD values are {1, 2, 3, 6, 10}, which corresponds to these subsequences:
- GCD = 1: [6, 10, 3] or [10, 3]
- GCD = 2: [6, 10]
- GCD = 3: [6, 3] or [3]
- GCD = 6: [6]
- GCD = 10: [10]
Solution Implementation
1from typing import List
2from math import gcd
3
4class Solution:
5 def countDifferentSubsequenceGCDs(self, nums: List[int]) -> int:
6 # Find the maximum value in the array to set our search boundary
7 max_value = max(nums)
8
9 # Convert nums to a set for O(1) membership checking
10 num_set = set(nums)
11
12 # Counter for distinct GCD values
13 distinct_gcd_count = 0
14
15 # Try each possible GCD value from 1 to max_value
16 for potential_gcd in range(1, max_value + 1):
17 # Current GCD of all multiples of potential_gcd found in num_set
18 current_gcd = 0
19
20 # Check all multiples of potential_gcd up to max_value
21 for multiple in range(potential_gcd, max_value + 1, potential_gcd):
22 # If this multiple exists in our original array
23 if multiple in num_set:
24 # Update the GCD with this multiple
25 current_gcd = gcd(current_gcd, multiple)
26
27 # If we've found that potential_gcd is achievable as a GCD
28 if current_gcd == potential_gcd:
29 distinct_gcd_count += 1
30 break # No need to check more multiples
31
32 return distinct_gcd_count
33
1class Solution {
2 /**
3 * Count the number of different GCDs among all non-empty subsequences of the array.
4 *
5 * @param nums the input array of positive integers
6 * @return the number of different possible GCDs
7 */
8 public int countDifferentSubsequenceGCDs(int[] nums) {
9 // Find the maximum value in the array
10 int maxValue = Arrays.stream(nums).max().getAsInt();
11
12 // Create a boolean array to mark which numbers exist in the input array
13 boolean[] exists = new boolean[maxValue + 1];
14 for (int num : nums) {
15 exists[num] = true;
16 }
17
18 int count = 0;
19
20 // Try each possible GCD value from 1 to maxValue
21 for (int candidateGcd = 1; candidateGcd <= maxValue; candidateGcd++) {
22 int currentGcd = 0;
23
24 // Check all multiples of candidateGcd that exist in the array
25 for (int multiple = candidateGcd; multiple <= maxValue; multiple += candidateGcd) {
26 if (exists[multiple]) {
27 // Calculate GCD of current result with this multiple
28 currentGcd = gcd(currentGcd, multiple);
29
30 // If the GCD equals our candidate, we found a valid subsequence
31 if (currentGcd == candidateGcd) {
32 count++;
33 break;
34 }
35 }
36 }
37 }
38
39 return count;
40 }
41
42 /**
43 * Calculate the greatest common divisor using Euclidean algorithm.
44 *
45 * @param a first number
46 * @param b second number
47 * @return the GCD of a and b
48 */
49 private int gcd(int a, int b) {
50 return b == 0 ? a : gcd(b, a % b);
51 }
52}
53
1class Solution {
2public:
3 int countDifferentSubsequenceGCDs(vector<int>& nums) {
4 // Find the maximum element in the array
5 int maxValue = *max_element(nums.begin(), nums.end());
6
7 // Create a boolean array to mark which numbers exist in the input
8 vector<bool> exists(maxValue + 1, false);
9
10 // Mark all numbers that appear in the input array
11 for (int& num : nums) {
12 exists[num] = true;
13 }
14
15 // Count the number of different GCDs possible
16 int count = 0;
17
18 // Try each potential GCD value from 1 to maxValue
19 for (int potentialGCD = 1; potentialGCD <= maxValue; ++potentialGCD) {
20 int currentGCD = 0;
21
22 // Check all multiples of potentialGCD that exist in the array
23 for (int multiple = potentialGCD; multiple <= maxValue; multiple += potentialGCD) {
24 if (exists[multiple]) {
25 // Update the GCD with this multiple
26 currentGCD = gcd(currentGCD, multiple);
27
28 // If we've found that potentialGCD is achievable as a GCD
29 if (currentGCD == potentialGCD) {
30 ++count;
31 break; // No need to check further multiples
32 }
33 }
34 }
35 }
36
37 return count;
38 }
39};
40
1function countDifferentSubsequenceGCDs(nums: number[]): number {
2 // Find the maximum element in the array
3 const maxValue = Math.max(...nums);
4
5 // Create a boolean array to mark which numbers exist in the input
6 const exists: boolean[] = new Array(maxValue + 1).fill(false);
7
8 // Mark all numbers that appear in the input array
9 for (const num of nums) {
10 exists[num] = true;
11 }
12
13 // Count the number of different GCDs possible
14 let count = 0;
15
16 // Try each potential GCD value from 1 to maxValue
17 for (let potentialGCD = 1; potentialGCD <= maxValue; potentialGCD++) {
18 let currentGCD = 0;
19
20 // Check all multiples of potentialGCD that exist in the array
21 for (let multiple = potentialGCD; multiple <= maxValue; multiple += potentialGCD) {
22 if (exists[multiple]) {
23 // Update the GCD with this multiple
24 currentGCD = gcd(currentGCD, multiple);
25
26 // If we've found that potentialGCD is achievable as a GCD
27 if (currentGCD === potentialGCD) {
28 count++;
29 break; // No need to check further multiples
30 }
31 }
32 }
33 }
34
35 return count;
36}
37
38// Helper function to calculate the greatest common divisor
39function gcd(a: number, b: number): number {
40 // Use Euclidean algorithm to find GCD
41 while (b !== 0) {
42 const temp = b;
43 b = a % b;
44 a = temp;
45 }
46 return a;
47}
48
Time and Space Complexity
Time Complexity: O(n + M × log M)
The time complexity consists of two main parts:
O(n)
for creating the setvis
from the input arraynums
and finding the maximum valueO(M × log M)
for the nested loops:- The outer loop runs from
1
tomx
, contributingO(M)
iterations - For each value
x
in the outer loop, the inner loop iterates through multiples ofx
up tomx
, which takesO(M/x)
iterations - The total number of inner loop iterations across all outer loop iterations is
M/1 + M/2 + M/3 + ... + M/M = M × (1/1 + 1/2 + 1/3 + ... + 1/M)
, which equalsO(M × log M)
(harmonic series) - Each iteration involves a GCD operation
gcd(g, y)
which takesO(log M)
time, but since we break early wheng == x
, the amortized cost is absorbed in theO(M × log M)
bound
- The outer loop runs from
Space Complexity: O(M)
The space complexity is determined by:
- The set
vis
which stores at mostn
unique elements fromnums
, but in the worst case when all elements are distinct and equal toM
, this isO(M)
- Other variables (
mx
,ans
,g
, loop variables) useO(1)
space
Where n
is the length of the array nums
and M
is the maximum value in the array nums
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting to Generate All Subsequences Explicitly
Many developers initially try to solve this problem by generating all possible subsequences and calculating their GCDs:
# WRONG APPROACH - Time Limit Exceeded
def countDifferentSubsequenceGCDs(nums):
gcd_values = set()
n = len(nums)
# Generate all 2^n subsequences
for mask in range(1, 1 << n):
subsequence = []
for i in range(n):
if mask & (1 << i):
subsequence.append(nums[i])
# Calculate GCD of subsequence
g = subsequence[0]
for num in subsequence[1:]:
g = gcd(g, num)
gcd_values.add(g)
return len(gcd_values)
Why it fails: This approach has O(2^n) time complexity, which is exponential. For arrays with even moderate sizes (n > 20), this becomes computationally infeasible.
Solution: Instead of generating subsequences, iterate through potential GCD values and check if each is achievable by finding its multiples in the array.
2. Incorrect GCD Calculation Logic
A subtle bug can occur when calculating the running GCD:
# WRONG - Incorrect initialization
for potential_gcd in range(1, max_value + 1):
current_gcd = potential_gcd # Wrong initialization!
for multiple in range(potential_gcd, max_value + 1, potential_gcd):
if multiple in num_set:
current_gcd = gcd(current_gcd, multiple)
Why it fails: Initializing current_gcd
to potential_gcd
assumes we've already found one element, which may not exist in the array. This can lead to counting GCD values that aren't actually achievable.
Solution: Initialize current_gcd
to 0. The mathematical property gcd(0, n) = n
ensures correct behavior when we find the first multiple.
3. Forgetting Edge Cases with Single Elements
Some implementations forget that single-element subsequences are valid:
# WRONG - Missing single elements
for potential_gcd in range(1, max_value + 1):
current_gcd = 0
found_count = 0
for multiple in range(potential_gcd, max_value + 1, potential_gcd):
if multiple in num_set:
found_count += 1
current_gcd = gcd(current_gcd, multiple)
# Wrong condition - requires at least 2 elements
if found_count >= 2 and current_gcd == potential_gcd:
distinct_gcd_count += 1
Why it fails: Every single element in the array forms a valid subsequence with GCD equal to itself. Requiring multiple elements misses these cases.
Solution: Remove the constraint on the number of elements found. Any non-zero number of multiples that produce the target GCD is valid.
4. Not Using Early Termination Optimization
While not incorrect, failing to break early can impact performance:
# CORRECT but less efficient
for potential_gcd in range(1, max_value + 1):
current_gcd = 0
for multiple in range(potential_gcd, max_value + 1, potential_gcd):
if multiple in num_set:
current_gcd = gcd(current_gcd, multiple)
# Only check at the end - misses optimization opportunity
if current_gcd == potential_gcd:
distinct_gcd_count += 1
Why it's suboptimal: Once current_gcd
equals potential_gcd
, checking additional multiples won't change this fact (the GCD can only stay the same or decrease).
Solution: Check if current_gcd == potential_gcd
after each update and break immediately when true. This can significantly reduce unnecessary iterations.
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!