2470. Number of Subarrays With LCM Equal to K
Problem Description
You are given an integer array nums
and an integer k
. Your task is to find how many subarrays have a least common multiple (LCM) equal to k
.
A subarray is a contiguous sequence of elements from the array (cannot be empty). For example, if nums = [1, 2, 3]
, then [1]
, [2]
, [3]
, [1, 2]
, [2, 3]
, and [1, 2, 3]
are all valid subarrays.
The least common multiple of an array is the smallest positive integer that can be divided evenly by all elements in that array. For instance, the LCM of [2, 3]
is 6
because 6 is the smallest number divisible by both 2 and 3.
The solution uses a nested loop approach. For each starting position i
in the array, it considers all possible subarrays beginning at that position. It maintains a running LCM value a
that gets updated as each new element b
is included in the subarray. The LCM is calculated using the built-in lcm()
function. Whenever the LCM equals k
, the answer counter is incremented. This process continues for all possible subarrays, and the final count is returned.
The time complexity is O(n²)
where n
is the length of the array, since we examine all possible subarrays. The key insight is that we can efficiently compute the LCM of each subarray by updating the previous LCM value rather than recalculating from scratch.
Intuition
When we need to find subarrays with a specific property, a natural starting point is to check all possible subarrays. Since a subarray is defined by its starting and ending positions, we can use two nested loops to generate all combinations.
The key observation is that the LCM has an interesting property: as we extend a subarray by adding one more element, we can compute the new LCM using the previous LCM and the new element. We don't need to recalculate the LCM from scratch for the entire subarray. This is because LCM(a, b, c) = LCM(LCM(a, b), c)
.
Another important insight is that the LCM of a subarray will either stay the same or increase as we add more elements (it never decreases). This is because the LCM must be divisible by all elements, so adding a new element can only make it larger or keep it the same. Once the LCM exceeds k
, we could potentially optimize by breaking early since it will never come back down to k
.
The brute force approach works well here because:
- We need to check every subarray anyway to count exact matches with
k
- The LCM calculation can be done incrementally as we extend each subarray
- The problem likely has reasonable constraints on array size
By fixing the starting position and incrementally extending the subarray while updating the LCM, we efficiently explore all possibilities and count those where LCM == k
.
Learn more about Math patterns.
Solution Approach
The solution uses an enumeration strategy to check all possible subarrays. Here's how the implementation works:
-
Outer Loop - Starting Position: We iterate through each index
i
from0
ton-1
as the starting position of our subarray. This ensures we consider all possible starting points. -
Initialize LCM Tracker: For each starting position
i
, we initialize variablea
withnums[i]
. This variable will track the running LCM of the current subarray. -
Inner Loop - Extending Subarray: We iterate through elements from position
i
to the end of the array usingnums[i:]
. Each elementb
represents a potential ending element of our subarray. -
LCM Calculation: For each new element
b
, we calculatex = lcm(a, b)
. This gives us the LCM of the subarray from positioni
to the current element's position. Thelcm()
function efficiently computes the least common multiple of two numbers. -
Check and Count: We check if
x == k
. If true, we increment our answer counter by 1 using the expressionans += x == k
(which adds 1 when True, 0 when False). -
Update Running LCM: We update
a = x
to prepare for the next iteration. This allows us to incrementally build the LCM rather than recalculating from scratch.
The algorithm examines subarrays in the following order for an array [a, b, c]
:
- Start at
a
: check[a]
, then[a,b]
, then[a,b,c]
- Start at
b
: check[b]
, then[b,c]
- Start at
c
: check[c]
This systematic approach ensures we count every subarray exactly once and efficiently compute their LCMs using the previous result.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [6, 2, 3]
and k = 6
.
Starting at index 0 (element 6):
- Initialize
a = 6
(first element) - Subarray
[6]
: Calculatex = lcm(6, 6) = 6
. Sincex == k
(6 == 6), increment counter:ans = 1
. Updatea = 6
. - Subarray
[6, 2]
: Calculatex = lcm(6, 2) = 6
. Sincex == k
(6 == 6), increment counter:ans = 2
. Updatea = 6
. - Subarray
[6, 2, 3]
: Calculatex = lcm(6, 3) = 6
. Sincex == k
(6 == 6), increment counter:ans = 3
. Updatea = 6
.
Starting at index 1 (element 2):
- Initialize
a = 2
(first element) - Subarray
[2]
: Calculatex = lcm(2, 2) = 2
. Sincex != k
(2 != 6), no increment. Updatea = 2
. - Subarray
[2, 3]
: Calculatex = lcm(2, 3) = 6
. Sincex == k
(6 == 6), increment counter:ans = 4
. Updatea = 6
.
Starting at index 2 (element 3):
- Initialize
a = 3
(first element) - Subarray
[3]
: Calculatex = lcm(3, 3) = 3
. Sincex != k
(3 != 6), no increment. Updatea = 3
.
Final answer: 4 subarrays have LCM equal to 6
The key insight demonstrated here is how we efficiently compute the LCM incrementally. For instance, when extending [6]
to [6, 2]
, we don't recalculate from scratch but use lcm(6, 2)
where 6 was our previous LCM. This incremental approach allows us to check all subarrays in O(n²) time while maintaining the running LCM value.
Solution Implementation
1from typing import List
2from math import gcd
3
4class Solution:
5 def subarrayLCM(self, nums: List[int], k: int) -> int:
6 """
7 Count the number of subarrays where the LCM of all elements equals k.
8
9 Args:
10 nums: List of positive integers
11 k: Target LCM value
12
13 Returns:
14 Number of subarrays with LCM equal to k
15 """
16
17 def lcm(a: int, b: int) -> int:
18 """Calculate the least common multiple of two numbers."""
19 return abs(a * b) // gcd(a, b)
20
21 n = len(nums)
22 count = 0
23
24 # Iterate through each possible starting position
25 for start_idx in range(n):
26 current_lcm = nums[start_idx]
27
28 # Extend the subarray from the current starting position
29 for num in nums[start_idx:]:
30 # Update LCM to include the current number
31 current_lcm = lcm(current_lcm, num)
32
33 # Check if current subarray's LCM equals target
34 if current_lcm == k:
35 count += 1
36
37 # Early termination: if LCM exceeds k, it won't decrease
38 if current_lcm > k:
39 break
40
41 return count
42
1class Solution {
2 /**
3 * Counts the number of subarrays whose LCM equals k.
4 *
5 * @param nums the input array of positive integers
6 * @param k the target LCM value
7 * @return the count of subarrays with LCM equal to k
8 */
9 public int subarrayLCM(int[] nums, int k) {
10 int arrayLength = nums.length;
11 int count = 0;
12
13 // Iterate through each starting position of subarray
14 for (int startIndex = 0; startIndex < arrayLength; ++startIndex) {
15 int currentLCM = nums[startIndex];
16
17 // Extend the subarray from startIndex to endIndex
18 for (int endIndex = startIndex; endIndex < arrayLength; ++endIndex) {
19 int currentElement = nums[endIndex];
20
21 // Calculate LCM of current subarray
22 int newLCM = lcm(currentLCM, currentElement);
23
24 // Check if the LCM equals target value
25 if (newLCM == k) {
26 ++count;
27 }
28
29 // Update current LCM for next iteration
30 currentLCM = newLCM;
31 }
32 }
33
34 return count;
35 }
36
37 /**
38 * Calculates the Least Common Multiple (LCM) of two numbers.
39 * Uses the formula: LCM(a, b) = (a * b) / GCD(a, b)
40 *
41 * @param a first positive integer
42 * @param b second positive integer
43 * @return the LCM of a and b
44 */
45 private int lcm(int a, int b) {
46 return a * b / gcd(a, b);
47 }
48
49 /**
50 * Calculates the Greatest Common Divisor (GCD) using Euclidean algorithm.
51 *
52 * @param a first positive integer
53 * @param b second positive integer
54 * @return the GCD of a and b
55 */
56 private int gcd(int a, int b) {
57 return b == 0 ? a : gcd(b, a % b);
58 }
59}
60
1class Solution {
2public:
3 int subarrayLCM(vector<int>& nums, int k) {
4 int n = nums.size();
5 int count = 0; // Counter for subarrays with LCM equal to k
6
7 // Iterate through all possible starting positions of subarrays
8 for (int i = 0; i < n; ++i) {
9 int currentLCM = nums[i]; // Initialize LCM with the first element of subarray
10
11 // Extend the subarray from position i to j
12 for (int j = i; j < n; ++j) {
13 // Calculate LCM of current subarray [i...j]
14 currentLCM = lcm(currentLCM, nums[j]);
15
16 // If LCM equals k, increment the counter
17 if (currentLCM == k) {
18 count++;
19 }
20
21 // Early termination: if LCM exceeds k, no point in extending further
22 // since LCM can only increase or stay the same
23 if (currentLCM > k) {
24 break;
25 }
26 }
27 }
28
29 return count;
30 }
31};
32
1function subarrayLCM(nums: number[], k: number): number {
2 const n: number = nums.length;
3 let count: number = 0; // Counter for subarrays with LCM equal to k
4
5 // Iterate through all possible starting positions of subarrays
6 for (let i = 0; i < n; i++) {
7 let currentLcm: number = nums[i]; // Initialize LCM with the first element of subarray
8
9 // Extend the subarray from position i to j
10 for (let j = i; j < n; j++) {
11 // Calculate LCM of current subarray [i...j]
12 currentLcm = lcm(currentLcm, nums[j]);
13
14 // If LCM equals k, increment the counter
15 if (currentLcm === k) {
16 count++;
17 }
18
19 // Early termination: if LCM exceeds k, no point in extending further
20 // since LCM can only increase or stay the same
21 if (currentLcm > k) {
22 break;
23 }
24 }
25 }
26
27 return count;
28}
29
30// Helper function to calculate the greatest common divisor using Euclidean algorithm
31function gcd(a: number, b: number): number {
32 while (b !== 0) {
33 const temp: number = b;
34 b = a % b;
35 a = temp;
36 }
37 return a;
38}
39
40// Helper function to calculate the least common multiple using the GCD
41function lcm(a: number, b: number): number {
42 return (a * b) / gcd(a, b);
43}
44
Time and Space Complexity
Time Complexity: O(n²)
The code uses two nested loops to examine all subarrays:
- The outer loop runs
n
times (fromi = 0
ton-1
) - For each iteration
i
, the inner loop iterates throughnums[i:]
, which means:- When
i = 0
: inner loop runsn
times - When
i = 1
: inner loop runsn-1
times - When
i = n-1
: inner loop runs1
time
- When
- Total iterations:
n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)
Within each iteration, the lcm(a, b)
operation can be considered O(log(min(a,b)))
in the worst case (using GCD calculation via Euclidean algorithm), but since the problem likely has bounded integer values, we can treat this as O(1)
for practical purposes.
Therefore, the overall time complexity is O(n²)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Variables
n
,ans
,i
,a
,b
, andx
each useO(1)
space - No additional data structures are created
- The iteration through
nums[i:]
doesn't create a new array, it just iterates through the existing array
The space complexity is O(1)
(constant space).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. LCM Overflow and Early Termination
One critical pitfall in this solution is not properly handling the case where the LCM grows beyond k
. Once the LCM of a subarray exceeds k
, extending that subarray further will never reduce the LCM back to k
(since LCM can only increase or stay the same when adding elements). The code includes an optimization with if current_lcm > k: break
, but there's a subtlety here.
Problem: If k
is not divisible by an element in the array, the LCM will never equal k
for any subarray containing that element.
Example: If nums = [3, 6, 2]
and k = 6
:
- Starting at index 0: LCM([3]) = 3, LCM([3,6]) = 6 ✓, LCM([3,6,2]) = 6 ✓
- But if
nums = [3, 7, 2]
andk = 6
: - Starting at index 0: LCM([3]) = 3, LCM([3,7]) = 21 > 6 (breaks correctly)
Better approach: Add an additional check before processing:
# Optimization: k must be divisible by every element in a valid subarray
for start_idx in range(n):
if k % nums[start_idx] != 0:
continue # Skip this starting position entirely
current_lcm = nums[start_idx]
# ... rest of the logic
2. Integer Overflow in LCM Calculation
The LCM calculation a * b // gcd(a, b)
can cause integer overflow when a
and b
are large, even if their LCM would fit in an integer.
Problem: Multiplying two large numbers before division can exceed integer limits.
Solution: Check for potential overflow or add bounds:
def lcm(a: int, b: int) -> int:
g = gcd(a, b)
# Check if multiplication would overflow
if a > k or b > k: # LCM will definitely exceed k
return k + 1 # Return any value > k to trigger early termination
return (a // g) * b # Divide first to reduce overflow risk
3. Missing Edge Case: Single Element Subarrays
While the current implementation handles single-element subarrays correctly, a common mistake when modifying this code is to accidentally skip them by starting the inner loop at start_idx + 1
instead of start_idx
.
Incorrect modification:
for end_idx in range(start_idx + 1, n): # Wrong! Skips single elements
current_lcm = lcm(current_lcm, nums[end_idx])
Correct approach (as in the original):
for num in nums[start_idx:]: # Includes single element subarrays
4. Inefficient LCM Recalculation
A tempting but inefficient approach would be to recalculate the LCM from scratch for each subarray:
Inefficient:
for i in range(n):
for j in range(i, n):
subarray_lcm = nums[i]
for k in range(i+1, j+1): # Recalculating from scratch
subarray_lcm = lcm(subarray_lcm, nums[k])
The original solution correctly maintains a running LCM, which is much more efficient.
Which data structure is used to implement recursion?
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!