LeetCode 2197 - Replace Non-Coprime Numbers in Array
The problem asks us to repeatedly replace adjacent non-coprime numbers in an array with their Least Common Multiple (LCM) until no more adjacent non-coprime pairs exist. Two numbers are non-coprime if their greatest common divisor (GCD) is greater than 1.
Difficulty: 🔴 Hard
Topics: Array, Math, Stack, Number Theory
Solution
Problem Understanding
The problem asks us to repeatedly replace adjacent non-coprime numbers in an array with their Least Common Multiple (LCM) until no more adjacent non-coprime pairs exist. Two numbers are non-coprime if their greatest common divisor (GCD) is greater than 1. The final array should be returned after all possible replacements.
In other words, given an array of integers, we must iteratively combine numbers that share any common factor larger than 1. Once two numbers are combined into their LCM, it may create new non-coprime pairs with the adjacent numbers, so the process continues until no further combinations are possible. The array length can go up to 100,000, and each integer can be as large as 100,000, but the final array values are guaranteed to be less than or equal to 108.
Key edge cases include arrays where all numbers are coprime, arrays of length 1, arrays with repeated numbers, or arrays where repeated replacements can occur, requiring a careful iterative approach.
Approaches
A brute-force approach would be to iterate through the array from left to right, compute the GCD for each adjacent pair, and replace non-coprime pairs with their LCM. After each replacement, the iteration would restart from the previous index to check for newly formed non-coprime pairs. While this works for small arrays, it can be extremely inefficient for large arrays because repeatedly recomputing GCDs for the same pairs can result in O(n^2) time complexity.
The optimal solution leverages a stack to maintain a running list of numbers. For each new number, we repeatedly compare it with the top of the stack. If they are non-coprime, we replace the top element with the LCM of the two and continue this check until the new top of the stack is coprime with the current number. If coprime, we push the number onto the stack. This approach avoids restarting iterations and guarantees linear time complexity on average because each element is processed once with occasional repeated LCM calculations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(n) | Iterate repeatedly over the array, recomputing GCDs for adjacent elements after each replacement. |
| Optimal (Stack) | O(n log max(num)) | O(n) | Use a stack to combine adjacent non-coprime numbers efficiently. LCM/GCD calculations dominate logarithmic factor. |
Algorithm Walkthrough
- Initialize an empty stack to hold numbers that have been processed so far.
- Iterate over each number in the input array.
- While the stack is not empty and the top of the stack and the current number are non-coprime (GCD > 1), replace the top of the stack with the LCM of the top element and the current number. Update the current number with this LCM because the combined value may form new non-coprime pairs with the previous stack element.
- Once the top of the stack is coprime with the current number or the stack is empty, push the current number onto the stack.
- After processing all numbers, the stack contains the final array with all adjacent non-coprime numbers combined.
Why it works: The stack ensures that each new number is always compared with its left neighbor, preserving adjacency checks. Repeated LCM replacements propagate through the stack until all non-coprime pairs are resolved. Since the problem guarantees that the final values are less than 108, repeated LCM calculations will not overflow.
Python Solution
from math import gcd
from typing import List
class Solution:
def replaceNonCoprimes(self, nums: List[int]) -> List[int]:
stack = []
for num in nums:
while stack:
top = stack[-1]
g = gcd(top, num)
if g == 1:
break
# Replace top with LCM(top, num)
num = top * num // g
stack.pop()
stack.append(num)
return stack
The code initializes an empty stack and iterates over each number. It computes the GCD with the top element of the stack, and if they are non-coprime, it replaces the top element with their LCM. This continues until the top element is coprime with the current number or the stack is empty, ensuring that all non-coprime pairs are resolved efficiently.
Go Solution
package main
func gcd(a, b int) int {
for b != 0 {
a, b = b, a % b
}
return a
}
func replaceNonCoprimes(nums []int) []int {
stack := []int{}
for _, num := range nums {
for len(stack) > 0 {
top := stack[len(stack)-1]
g := gcd(top, num)
if g == 1 {
break
}
num = top * num / g
stack = stack[:len(stack)-1]
}
stack = append(stack, num)
}
return stack
}
In Go, slices are used to implement the stack. The gcd function uses the Euclidean algorithm. Unlike Python, Go requires explicit slice manipulation to pop elements, but the logic remains identical.
Worked Examples
Example 1: nums = [6,4,3,2,7,6,2]
| Step | Stack | Action |
|---|---|---|
| Start | [] | Initial empty stack |
| 6 | [6] | Push 6 |
| 4 | [12] | 6 and 4 GCD=2, replace with LCM=12 |
| 3 | [12] | 12 and 3 GCD=3, replace with LCM=12 |
| 2 | [12] | 12 and 2 GCD=2, replace with LCM=12 |
| 7 | [12,7] | 12 and 7 GCD=1, push 7 |
| 6 | [12,7,6] | 7 and 6 GCD=1, push 6 |
| 2 | [12,7,6] | 6 and 2 GCD=2, replace with LCM=6 |
Final stack: [12,7,6]
Example 2: nums = [2,2,1,1,3,3,3]
| Step | Stack | Action |
|---|---|---|
| Start | [] | Initial empty stack |
| 2 | [2] | Push 2 |
| 2 | [2] | 2 and 2 GCD=2, replace with LCM=2 |
| 1 | [2,1] | 2 and 1 GCD=1, push 1 |
| 1 | [2,1,1] | 1 and 1 GCD=1, push 1 |
| 3 | [2,1,1,3] | 1 and 3 GCD=1, push 3 |
| 3 | [2,1,1,3] | 3 and 3 GCD=3, replace with LCM=3 |
| 3 | [2,1,1,3] | 3 and 3 GCD=3, replace with LCM=3 |
Final stack: [2,1,1,3]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log M) | Each element is pushed and popped at most once. GCD computation takes O(log M) where M is the maximum number value (≤105). |
| Space | O(n) | Stack stores at most all elements of the array. |
The time complexity accounts for the Euclidean GCD algorithm, which is logarithmic in the size of the numbers, making the solution efficient even for large arrays.
Test Cases
# Provided examples
assert Solution().replaceNonCoprimes([6,4,3,2,7,6,2]) == [12,7,6] # Example 1
assert Solution().replaceNonCoprimes([2,2,1,1,3,3,3]) == [2,1,1,3] # Example 2
# Edge cases
assert Solution().replaceNonCoprimes([1]) == [1] # Single element
assert Solution().replaceNonCoprimes([2,3,5,7]) == [2,3,5,7] # All coprime
assert Solution().replaceNonCoprimes([4,6,8]) == [24] # All numbers combine
assert Solution().replaceNonCoprimes([2,4,8,16]) == [16] # Powers of 2
assert Solution().replaceNonCoprimes([3,9,27,81]) == [81] # Powers of 3
assert Solution().replaceNonCoprimes([5,10,15,20,25]) == [300] # Mixed multiples
| Test | Why |
|---|---|
[6,4,3,2,7,6,2] |
Standard case with multiple replacements |
[2,2,1,1,3,3,3] |
Repeated elements and ones |
[1] |
Minimum length input |
| `[2,3,5, |