Facebook Pixel

2447. Number of Subarrays With GCD Equal to K

MediumArrayMathNumber Theory
Leetcode Link

Problem Description

You are given an integer array nums and an integer k. Your task is to find and count how many subarrays have a greatest common divisor (GCD) equal to k.

A subarray is a contiguous sequence of elements from the array. For example, if nums = [1, 2, 3], then [1], [2], [3], [1, 2], [2, 3], and [1, 2, 3] are all possible subarrays.

The greatest common divisor (GCD) of an array is the largest positive integer that divides all elements in that array evenly. For instance, the GCD of [6, 12, 18] is 6 because 6 is the largest number that divides all three numbers without remainder.

The solution works by checking every possible subarray. For each starting position i, it examines all subarrays beginning at that position. It maintains a running GCD value g that gets updated as each new element is included in the subarray. When extending the subarray to include nums[j], the GCD is updated using g = gcd(g, nums[j]). Whenever the current GCD equals k, the counter is incremented by 1.

The algorithm uses the property that the GCD of a set of numbers can be computed incrementally: gcd(a, b, c) = gcd(gcd(a, b), c). This allows efficient calculation of the GCD for each subarray without recomputing from scratch.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to check every possible subarray to determine if its GCD equals k. Since a subarray is defined by its starting and ending positions, we can systematically examine all possibilities.

The brute force approach naturally emerges when we think about how to generate all subarrays: pick a starting point, then extend the subarray one element at a time to the right. For each extension, we check if the GCD of the current subarray equals k.

The clever part is recognizing that we don't need to recalculate the GCD from scratch for each subarray. When we extend a subarray by adding one more element, we can update the GCD incrementally. This works because of the mathematical property: gcd(a, b, c) = gcd(gcd(a, b), c).

Starting with g = 0 is another smart trick. Since gcd(0, x) = x for any positive integer x, initializing g = 0 means that after processing the first element, g will equal that element. This eliminates the need for special handling of the first element.

As we extend each subarray, the GCD can only stay the same or decrease (it can never increase when adding more numbers). This property means that once the GCD drops below k, we might still find subarrays with GCD equal to k by continuing to add elements, but only if those elements are multiples of k.

The nested loop structure naturally captures this process: the outer loop fixes the starting position, while the inner loop extends the subarray element by element, updating the GCD and counting matches along the way.

Learn more about Math patterns.

Solution Approach

The solution uses a direct enumeration approach with nested loops to examine all possible subarrays.

Algorithm Steps:

  1. Initialize a counter ans = 0 to track the number of subarrays with GCD equal to k.

  2. Use the outer loop to iterate through each possible starting position i from 0 to len(nums) - 1.

  3. For each starting position i, initialize g = 0 to track the GCD of the current subarray. Setting g = 0 initially is a trick since gcd(0, x) = x for any positive integer x.

  4. Use an inner loop to iterate through elements from position i to the end of the array. This loop extends the subarray one element at a time by including nums[j] where j goes from i to len(nums) - 1.

  5. For each new element x in the inner loop:

    • Update the GCD: g = gcd(g, x)
    • Check if the current GCD equals k: if g == k, increment the answer by 1
  6. After both loops complete, return ans.

Key Implementation Details:

  • The code uses Python's built-in gcd function from the math module to compute the greatest common divisor.
  • The expression nums[i:] creates a slice starting from index i to the end, which the inner loop iterates through.
  • The line ans += g == k is a Pythonic way of incrementing the counter when the condition is true (since True equals 1 in Python).

Time Complexity: O(n² × log(max(nums))) where n is the length of the array. The nested loops give O(n²), and each GCD computation takes O(log(max(nums))) time.

Space Complexity: O(1) as we only use a constant amount of extra space for variables ans and g.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [6, 3, 12, 9] and k = 3.

We'll examine all possible subarrays and track their GCDs:

Starting at index 0 (element 6):

  • Initialize g = 0
  • Subarray [6]: g = gcd(0, 6) = 6. Since 6 ≠ 3, don't count it.
  • Subarray [6, 3]: g = gcd(6, 3) = 3. Since 3 = 3, increment counter to 1.
  • Subarray [6, 3, 12]: g = gcd(3, 12) = 3. Since 3 = 3, increment counter to 2.
  • Subarray [6, 3, 12, 9]: g = gcd(3, 9) = 3. Since 3 = 3, increment counter to 3.

Starting at index 1 (element 3):

  • Initialize g = 0
  • Subarray [3]: g = gcd(0, 3) = 3. Since 3 = 3, increment counter to 4.
  • Subarray [3, 12]: g = gcd(3, 12) = 3. Since 3 = 3, increment counter to 5.
  • Subarray [3, 12, 9]: g = gcd(3, 9) = 3. Since 3 = 3, increment counter to 6.

Starting at index 2 (element 12):

  • Initialize g = 0
  • Subarray [12]: g = gcd(0, 12) = 12. Since 12 ≠ 3, don't count it.
  • Subarray [12, 9]: g = gcd(12, 9) = 3. Since 3 = 3, increment counter to 7.

Starting at index 3 (element 9):

  • Initialize g = 0
  • Subarray [9]: g = gcd(0, 9) = 9. Since 9 ≠ 3, don't count it.

Final answer: 7 subarrays have GCD equal to 3

The subarrays with GCD = 3 are: [6,3], [6,3,12], [6,3,12,9], [3], [3,12], [3,12,9], and [12,9].

Solution Implementation

1from typing import List
2from math import gcd
3
4class Solution:
5    def subarrayGCD(self, nums: List[int], k: int) -> int:
6        """
7        Count the number of subarrays whose GCD equals k.
8      
9        Args:
10            nums: List of positive integers
11            k: Target GCD value
12          
13        Returns:
14            Number of subarrays with GCD equal to k
15        """
16        count = 0
17      
18        # Iterate through each starting position of subarray
19        for start_idx in range(len(nums)):
20            current_gcd = 0
21          
22            # Extend subarray from start_idx to the end
23            # Calculate GCD incrementally for each subarray
24            for num in nums[start_idx:]:
25                # GCD of 0 and any number is that number itself
26                # This handles the first element elegantly
27                current_gcd = gcd(current_gcd, num)
28              
29                # If current subarray's GCD equals k, increment count
30                if current_gcd == k:
31                    count += 1
32                  
33        return count
34
1class Solution {
2    /**
3     * Counts the number of subarrays whose greatest common divisor (GCD) equals k.
4     * 
5     * @param nums the input array of integers
6     * @param k the target GCD value
7     * @return the count of subarrays with GCD equal to k
8     */
9    public int subarrayGCD(int[] nums, int k) {
10        int arrayLength = nums.length;
11        int subarrayCount = 0;
12      
13        // Iterate through all possible starting positions
14        for (int startIndex = 0; startIndex < arrayLength; startIndex++) {
15            int currentGCD = 0;
16          
17            // Extend the subarray from startIndex to the end
18            for (int endIndex = startIndex; endIndex < arrayLength; endIndex++) {
19                // Update the GCD to include the current element
20                currentGCD = gcd(currentGCD, nums[endIndex]);
21              
22                // If the GCD of the current subarray equals k, increment the count
23                if (currentGCD == k) {
24                    subarrayCount++;
25                }
26            }
27        }
28      
29        return subarrayCount;
30    }
31  
32    /**
33     * Calculates the greatest common divisor (GCD) of two integers using Euclidean algorithm.
34     * 
35     * @param a the first integer
36     * @param b the second integer
37     * @return the GCD of a and b
38     */
39    private int gcd(int a, int b) {
40        // Base case: when b is 0, the GCD is a
41        // Recursive case: GCD(a, b) = GCD(b, a mod b)
42        return b == 0 ? a : gcd(b, a % b);
43    }
44}
45
1class Solution {
2public:
3    int subarrayGCD(vector<int>& nums, int k) {
4        int arraySize = nums.size();
5        int countSubarrays = 0;
6      
7        // Iterate through each starting position of subarray
8        for (int startIndex = 0; startIndex < arraySize; ++startIndex) {
9            int currentGCD = 0;
10          
11            // Extend the subarray from startIndex to each possible ending position
12            for (int endIndex = startIndex; endIndex < arraySize; ++endIndex) {
13                // Calculate GCD of current subarray by including nums[endIndex]
14                // Note: GCD(0, x) = x for any x, which handles the first element
15                currentGCD = gcd(currentGCD, nums[endIndex]);
16              
17                // If the GCD of current subarray equals k, increment counter
18                if (currentGCD == k) {
19                    countSubarrays++;
20                }
21            }
22        }
23      
24        return countSubarrays;
25    }
26};
27
1/**
2 * Counts the number of subarrays whose GCD equals k
3 * @param nums - The input array of numbers
4 * @param k - The target GCD value
5 * @returns The count of subarrays with GCD equal to k
6 */
7function subarrayGCD(nums: number[], k: number): number {
8    let count: number = 0;
9    const arrayLength: number = nums.length;
10  
11    // Iterate through all possible starting positions
12    for (let startIndex: number = 0; startIndex < arrayLength; startIndex++) {
13        let currentGCD: number = 0;
14      
15        // Extend the subarray from startIndex to each possible ending position
16        for (let endIndex: number = startIndex; endIndex < arrayLength; endIndex++) {
17            // Update GCD to include the current element
18            currentGCD = gcd(currentGCD, nums[endIndex]);
19          
20            // If the GCD of current subarray equals k, increment counter
21            if (currentGCD === k) {
22                count++;
23            }
24        }
25    }
26  
27    return count;
28}
29
30/**
31 * Calculates the Greatest Common Divisor (GCD) of two numbers using Euclidean algorithm
32 * @param a - First number
33 * @param b - Second number
34 * @returns The GCD of a and b
35 */
36function gcd(a: number, b: number): number {
37    // Base case: when b is 0, the GCD is a
38    // Recursive case: GCD(a, b) = GCD(b, a mod b)
39    return b === 0 ? a : gcd(b, a % b);
40}
41

Time and Space Complexity

Time Complexity: O(n × (n + log M)), where n is the length of the array nums and M is the maximum value in the array.

The analysis breaks down as follows:

  • The outer loop runs n times (iterating through each starting position i)
  • For each starting position i, the inner loop iterates through all elements from position i to the end, which takes O(n) iterations in the worst case
  • Within each inner loop iteration, we compute gcd(g, x), which takes O(log M) time where M is the maximum value being processed
  • The total number of GCD computations is n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
  • Therefore, the total time complexity is O(n² × log M) = O(n × n × log M) = O(n × (n + log M)) when simplified according to the reference answer's notation

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Variable ans to store the count
  • Variable g to store the running GCD
  • Loop variables i and x

All these variables use constant space regardless of the input size.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Early Termination Optimization Gone Wrong

A common mistake is attempting to optimize by breaking the inner loop when the GCD becomes less than k, thinking that adding more elements can't increase the GCD back to k.

Incorrect Implementation:

for start_idx in range(len(nums)):
    current_gcd = 0
    for num in nums[start_idx:]:
        current_gcd = gcd(current_gcd, num)
        if current_gcd < k:
            break  # WRONG: This causes early termination
        if current_gcd == k:
            count += 1

Why This Is Wrong: The GCD can only decrease or stay the same as we add more elements—it never increases. However, the GCD might start higher than k and later become equal to k. Breaking when current_gcd < k would miss valid subarrays.

Example:

  • nums = [12, 6, 3], k = 3
  • Subarray [12] has GCD = 12
  • Subarray [12, 6] has GCD = 6
  • Subarray [12, 6, 3] has GCD = 3 (equals k!)

If we broke when GCD < k, we'd never find the valid subarray [12, 6, 3].

Correct Optimization: Only break when the GCD becomes less than k AND k doesn't divide the current GCD:

for start_idx in range(len(nums)):
    current_gcd = 0
    for num in nums[start_idx:]:
        current_gcd = gcd(current_gcd, num)
        if current_gcd == k:
            count += 1
        elif current_gcd % k != 0:
            break  # Safe to break: GCD can't become k anymore

Pitfall 2: Misunderstanding GCD Calculation with Zero

Incorrect Assumption:

# Some might initialize with the first element
current_gcd = nums[start_idx]  
for num in nums[start_idx + 1:]:  # Skip first element
    current_gcd = gcd(current_gcd, num)

Why the Original Is Better: Starting with current_gcd = 0 and iterating through all elements (including the first) is cleaner because gcd(0, x) = x. This eliminates special case handling and makes the code more elegant.

Pitfall 3: Not Handling Edge Cases

Missing Validation:

def subarrayGCD(self, nums: List[int], k: int) -> int:
    # Should validate that k can be a valid GCD
    count = 0
    # ... rest of the code

Better Implementation with Validation:

def subarrayGCD(self, nums: List[int], k: int) -> int:
    # Early return if k cannot be a valid GCD
    if not any(num % k == 0 for num in nums):
        return 0
  
    count = 0
    for start_idx in range(len(nums)):
        current_gcd = 0
        for num in nums[start_idx:]:
            # Skip elements not divisible by k (optimization)
            if num % k != 0:
                break
            current_gcd = gcd(current_gcd, num)
            if current_gcd == k:
                count += 1
              
    return count

This optimization checks if any element is divisible by k first, and breaks the inner loop early when encountering an element not divisible by k, since the GCD can never be k if any element in the subarray isn't divisible by k.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More