LeetCode 2447 - Number of Subarrays With GCD Equal to K
The problem gives us an integer array nums and an integer k. We need to count how many contiguous subarrays have a greatest common divisor (GCD) exactly equal to k. A subarray is any non-empty contiguous segment of the array.
Difficulty: 🟡 Medium
Topics: Array, Math, Number Theory
Solution
LeetCode 2447 - Number of Subarrays With GCD Equal to K
Problem Understanding
The problem gives us an integer array nums and an integer k. We need to count how many contiguous subarrays have a greatest common divisor (GCD) exactly equal to k.
A subarray is any non-empty contiguous segment of the array. For every possible subarray, we compute the GCD of all elements inside it. If that GCD equals k, we count it.
For example, if a subarray contains values whose common divisor is exactly k, and no larger integer divides all of them, then that subarray contributes to the answer.
The constraints are important:
1 <= nums.length <= 10001 <= nums[i], k <= 10^9
The array length is relatively small, only up to 1000. This suggests that an O(n²) solution may be acceptable, while O(n³) would likely be too slow.
The values themselves can be very large, up to 10^9, which means we cannot rely on frequency arrays or value-based preprocessing. Instead, we should leverage mathematical properties of the GCD operation.
Several edge cases are worth considering:
- Arrays containing elements that are not divisible by
k. - Arrays where every element equals
k. - Single-element arrays.
- Cases where no valid subarray exists.
- Cases where the GCD quickly drops below
k, allowing early termination.
Approaches
Brute Force
The most direct approach is to enumerate every possible subarray.
For each starting index, we generate every ending index and compute the GCD of all elements inside that subarray from scratch. If the resulting GCD equals k, we increment the answer.
This approach is correct because it explicitly checks every possible subarray. However, computing the GCD of each subarray independently is expensive.
There are O(n²) subarrays, and computing the GCD of a subarray may require up to O(n) work. Therefore the total complexity becomes O(n³).
With n = 1000, this would require roughly one billion operations in the worst case, which is far too slow.
Key Insight
The key observation is that GCD has an incremental property:
$$\gcd(a,b,c)=\gcd(\gcd(a,b),c)$$
When extending a subarray by one element, we do not need to recompute the entire GCD. We can update the current GCD using only the previous GCD and the new element.
For every starting position:
- Initialize the current GCD as 0.
- Extend the subarray one element at a time.
- Update the running GCD.
Another important observation is that GCD values can only decrease or stay the same as more elements are added.
If the current GCD becomes smaller than k, it can never increase again. Therefore, no longer subarray starting from the same position can ever have GCD equal to k.
This allows an early break, significantly reducing unnecessary work.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Recomputes GCD for every subarray independently |
| Optimal | O(n² log M) | O(1) | Maintains running GCD while extending subarrays |
Here, M denotes the maximum value in the array.
Algorithm Walkthrough
Optimal Algorithm
- Initialize
answer = 0. - Iterate through every possible starting index
left. - For each starting index, initialize
current_gcd = 0. - Extend the subarray by moving the ending index
rightfromleftto the end of the array. - Update the running GCD:
$$current_gcd = \gcd(current_gcd,\ nums[right])$$
Using 0 initially works because:
$$\gcd(0,x)=x$$
6. If current_gcd == k, increment the answer because the current subarray satisfies the requirement.
7. If current_gcd < k, stop extending this starting position and move to the next one. Since GCD never increases when more elements are added, reaching a value below k means it can never become k again.
8. After all starting positions have been processed, return the answer.
Why it works
For a fixed starting index, the algorithm examines every possible ending index exactly once and maintains the exact GCD of that subarray using the associative property of GCD. Since every subarray is considered, every valid subarray is counted.
The early termination is valid because GCD values form a non-increasing sequence as elements are added. Once the GCD drops below k, it can never return to k, so no future extension can produce a valid subarray.
Python Solution
from typing import List
from math import gcd
class Solution:
def subarrayGCD(self, nums: List[int], k: int) -> int:
answer = 0
n = len(nums)
for left in range(n):
current_gcd = 0
for right in range(left, n):
current_gcd = gcd(current_gcd, nums[right])
if current_gcd == k:
answer += 1
elif current_gcd < k:
break
return answer
The implementation follows the algorithm directly.
The outer loop chooses every possible starting position. For each starting position, the inner loop extends the subarray one element at a time.
The variable current_gcd stores the GCD of the current subarray. Instead of recomputing the GCD from scratch, we update it incrementally using Python's built-in gcd function.
Whenever the running GCD becomes exactly k, we count the subarray. When it falls below k, we immediately stop processing that starting position because no further extension can restore the GCD to k.
This achieves the desired O(n² log M) complexity while using only constant extra space.
Go Solution
func subarrayGCD(nums []int, k int) int {
ans := 0
n := len(nums)
for left := 0; left < n; left++ {
currentGCD := 0
for right := left; right < n; right++ {
currentGCD = gcd(currentGCD, nums[right])
if currentGCD == k {
ans++
} else if currentGCD < k {
break
}
}
}
return ans
}
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
The Go implementation is almost identical to the Python version.
Go does not provide a built-in integer GCD function in the standard library for this use case, so we implement the Euclidean algorithm manually.
No special handling for nil slices is necessary because the problem guarantees at least one element. Integer overflow is not a concern because GCD computations never exceed the original input values.
Worked Examples
Example 1
Input
nums = [9,3,1,2,6,3]
k = 3
Valid subarrays are:
[9,3]
[3]
[3,1,2,6]
[3,1,2,6,3]
Answer = 4
Trace
| left | right | current_gcd | Count Added |
|---|---|---|---|
| 0 | 0 | 9 | No |
| 0 | 1 | 3 | Yes |
| 0 | 2 | 1 | Break |
| 1 | 1 | 3 | Yes |
| 1 | 2 | 1 | Break |
| 2 | 2 | 1 | Break |
| 3 | 3 | 2 | Break |
| 4 | 4 | 6 | No |
| 4 | 5 | 3 | Yes |
| 5 | 5 | 3 | Yes |
Total count = 4.
Example 2
Input
nums = [4]
k = 7
Trace
| left | right | current_gcd | Count Added |
|---|---|---|---|
| 0 | 0 | 4 | Break |
Since the GCD is already less than k, no valid subarray exists.
Answer = 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n² log M) | Each subarray extension performs one GCD computation |
| Space | O(1) | Only a few variables are maintained |
The outer loop runs n times and the inner loop can run up to n times, producing O(n²) iterations. Each iteration performs a GCD operation, whose complexity is O(log M) using the Euclidean algorithm. Therefore the total complexity is O(n² log M).
The algorithm uses only a constant number of extra variables, so the auxiliary space complexity is O(1).
Test Cases
from typing import List
s = Solution()
assert s.subarrayGCD([9, 3, 1, 2, 6, 3], 3) == 4 # official example
assert s.subarrayGCD([4], 7) == 0 # official example
assert s.subarrayGCD([3], 3) == 1 # single valid element
assert s.subarrayGCD([5], 3) == 0 # single invalid element
assert s.subarrayGCD([3, 3, 3], 3) == 6 # all subarrays valid
assert s.subarrayGCD([2, 4, 6], 1) == 0 # gcd never reaches k
assert s.subarrayGCD([6, 9, 12], 3) == 4 # multiple overlapping answers
assert s.subarrayGCD([5, 10, 15], 5) == 6 # every subarray valid
assert s.subarrayGCD([8, 16, 32], 4) == 0 # gcd always greater than k
assert s.subarrayGCD([7, 14, 21], 7) == 6 # all subarrays gcd 7
assert s.subarrayGCD([2, 3, 5, 7], 1) == 6 # gcd eventually becomes 1
assert s.subarrayGCD([1000000000], 1000000000) == 1 # maximum value
Test Summary
| Test | Why |
|---|---|
[9,3,1,2,6,3], k=3 |
Official example |
[4], k=7 |
No valid subarray |
[3], k=3 |
Single-element success |
[5], k=3 |
Single-element failure |
[3,3,3], k=3 |
Every subarray valid |
[2,4,6], k=1 |
GCD never reaches target |
[6,9,12], k=3 |
Mixed valid and invalid ranges |
[5,10,15], k=5 |
Every subarray shares same GCD |
[8,16,32], k=4 |
GCD always larger than target |
[7,14,21], k=7 |
Multiple valid subarrays |
[2,3,5,7], k=1 |
GCD gradually decreases to 1 |
[1000000000], k=1000000000 |
Maximum value boundary |
Edge Cases
Single-Element Arrays
When the array contains only one element, the answer depends entirely on whether that element equals k. A buggy implementation may accidentally assume every valid subarray has length at least two. The presented solution naturally handles this because every element forms a subarray by itself.
GCD Drops Below K Early
Consider nums = [9, 3, 1] and k = 3. After processing the third element, the GCD becomes 1. Since GCD values can only decrease, there is no reason to continue extending the subarray. The implementation correctly breaks out of the inner loop, improving efficiency without missing any valid answers.
Elements Not Divisible by K
Consider nums = [4] and k = 7. The GCD immediately becomes 4, which is already smaller than 7. No future extension could increase the GCD to 7. The algorithm correctly terminates early and returns zero.
All Elements Equal K
For nums = [k, k, k], every possible subarray has GCD equal to k. The implementation counts each one because the running GCD remains exactly k throughout every extension. This verifies that the algorithm handles the maximum number of valid subarrays correctly.
GCD Always Greater Than K
Consider nums = [8, 16, 32] and k = 4. Every subarray has GCD at least 8. The algorithm never increments the answer because the running GCD never becomes exactly 4, which is the required condition.