You are given a 0-indexed integer array nums, and you are allowed to traverse between its indices. You can traverse between index i and index j, i != j, if and only if gcd(nums[i], nums[j]) > 1, where gcd is the greatest common divisor.
Your task is to determine if for every pair of indices i and j in nums, where i < j, there exists a sequence of traversals that can take us from i to j.
Return true if it is possible to traverse between all such pairs of indices, or false otherwise.
Example 1:
Input: nums = [4,3,12]
Output: trueExplanation: There exists three possible pairsof indices. For each pair, the sequence of traversals are:
Example 2:
Input: nums = [2,3,7]
Output: falseConstraints:
1 <= nums.length <= 100,0001 <= nums[i] <= 100,000Two indices can be connected if their corresponding values share a common factor greater than 1. We can model this as a graph where each index is a node, and edges connect pairs with GCD > 1. The problem reduces to checking if all nodes belong to a single connected component.
i and j are connected if gcd(nums[i], nums[j]) > 1.0, marking all reachable nodes.true if all nodes are in one component, false otherwise.class Solution:
def canTraverseAllPairs(self, nums: List[int]) -> bool:
n = len(nums)
visit = [False] * n
adj = [[] for _ in range(n)]
for i in range(n):
for j in range(i + 1, n):
if gcd(nums[i], nums[j]) > 1:
adj[i].append(j)
adj[j].append(i)
def dfs(node):
visit[node] = True
for nei in adj[node]:
if not visit[nei]:
dfs(nei)
dfs(0)
for node in visit:
if not node:
return False
return TrueInstead of building explicit edges between indices, we can connect indices through their prime factors. Two numbers sharing a prime factor should be in the same component. Using Union-Find, we union each index with the first occurrence of each of its prime factors. This avoids O(n^2) pairwise comparisons.
n elements.class UnionFind:
def __init__(self, n):
self.n = n
self.Parent = list(range(n + 1))
self.Size = [1] * (n + 1)
def find(self, node):
if self.Parent[node] != node:
self.Parent[node] = self.find(self.Parent[node])
return self.Parent[node]
def union(self, u, v):
pu = self.find(u)
pv = self.find(v)
if pu == pv:
return False
self.n -= 1
if self.Size[pu] < self.Size[pv]:
pu, pv = pv, pu
self.Size[pu] += self.Size[pv]
self.Parent[pv] = pu
return True
def isConnected(self):
return self.n == 1
class Solution:
def canTraverseAllPairs(self, nums: List[int]) -> bool:
uf = UnionFind(len(nums))
factor_index = {} # f -> index of value with factor f
for i, n in enumerate(nums):
f = 2
while f * f <= n:
if n % f == 0:
if f in factor_index:
uf.union(i, factor_index[f])
else:
factor_index[f] = i
while n % f == 0:
n = n // f
f += 1
if n > 1:
if n in factor_index:
uf.union(i, factor_index[n])
else:
factor_index[n] = i
return uf.isConnected()Where is the size of the array and is the maximum value in the array.
We can speed up factorization by precomputing the smallest prime factor (SPF) for each number using the Sieve of Eratosthenes. With SPF, we can factorize any number in O(log m) time. We then use Union-Find to connect each index directly to virtual nodes representing primes.
true; any value of 1 returns false.sieve[x] stores the smallest prime factor of x.n + MAX + 1 (indices + virtual prime nodes).N + prime for each prime factor.class UnionFind:
def __init__(self, n):
self.Parent = list(range(n + 1))
self.Size = [1] * (n + 1)
def find(self, node):
if self.Parent[node] != node:
self.Parent[node] = self.find(self.Parent[node])
return self.Parent[node]
def union(self, u, v):
pu = self.find(u)
pv = self.find(v)
if pu == pv:
return False
if self.Size[pu] < self.Size[pv]:
pu, pv = pv, pu
self.Size[pu] += self.Size[pv]
self.Parent[pv] = pu
return True
class Solution:
def canTraverseAllPairs(self, nums: List[int]) -> bool:
N = len(nums)
if N == 1:
return True
if any(num == 1 for num in nums):
return False
MAX = max(nums)
sieve = [0] * (MAX + 1)
p = 2
while p * p <= MAX:
if sieve[p] == 0:
for composite in range(p * p, MAX + 1, p):
sieve[composite] = p
p += 1
uf = UnionFind(N + MAX + 1)
for i in range(N):
num = nums[i]
if sieve[num] == 0: # num is prime
uf.union(i, N + num)
continue
while num > 1:
prime = sieve[num] if sieve[num] != 0 else num
uf.union(i, N + prime)
while num % prime == 0:
num //= prime
root = uf.find(0)
for i in range(1, N):
if uf.find(i) != root:
return False
return TrueWhere is the size of the array and is the maximum value in the array.
Rather than Union-Find, we can build an explicit graph connecting indices to virtual prime nodes and use DFS for connectivity. Each index connects to nodes representing its prime factors, and primes connect back to indices. A single DFS from index 0 should reach all indices if they form one component.
true; any value of 1 returns false.i connects to N + prime for each of its prime factors, and vice versa.0, visiting all reachable nodes.true if all indices (0 to N-1) were visited.class Solution:
def canTraverseAllPairs(self, nums: List[int]) -> bool:
N = len(nums)
if N == 1:
return True
if any(num == 1 for num in nums):
return False
MAX = max(nums)
sieve = [0] * (MAX + 1)
p = 2
while p * p <= MAX:
if sieve[p] == 0:
for composite in range(p * p, MAX + 1, p):
sieve[composite] = p
p += 1
adj = defaultdict(list)
for i in range(N):
num = nums[i]
if sieve[num] == 0: # num is prime
adj[i].append(N + num)
adj[N + num].append(i)
continue
while num > 1:
prime = sieve[num] if sieve[num] != 0 else num
adj[i].append(N + prime)
adj[N + prime].append(i)
while num % prime == 0:
num //= prime
visited = set()
def dfs(node):
visited.add(node)
for nei in adj[node]:
if nei not in visited:
dfs(nei)
dfs(0)
for i in range(N):
if i not in visited:
return False
return TrueWhere is the size of the array and is the maximum value in the array.
BFS provides an iterative alternative to DFS for checking graph connectivity. We build the same graph structure connecting indices to prime nodes, then use a queue to explore all reachable nodes starting from index 0.
true; any value of 1 returns false.N).0 and a visited set.true if all indices (0 to N-1) were visited.class Solution:
def canTraverseAllPairs(self, nums: List[int]) -> bool:
N = len(nums)
if N == 1:
return True
if any(num == 1 for num in nums):
return False
MAX = max(nums)
sieve = [0] * (MAX + 1)
p = 2
while p * p <= MAX:
if sieve[p] == 0:
for composite in range(p * p, MAX + 1, p):
sieve[composite] = p
p += 1
adj = defaultdict(list)
for i in range(N):
num = nums[i]
if sieve[num] == 0: # num is prime
adj[i].append(N + num)
adj[N + num].append(i)
continue
while num > 1:
prime = sieve[num] if sieve[num] != 0 else num
adj[i].append(N + prime)
adj[N + prime].append(i)
while num % prime == 0:
num //= prime
visited = set()
queue = deque([0])
visited.add(0)
while queue:
node = queue.popleft()
for nei in adj[node]:
if nei not in visited:
visited.add(nei)
queue.append(nei)
for i in range(N):
if i not in visited:
return False
return TrueWhere is the size of the array and is the maximum value in the array.
The number 1 has no prime factors greater than 1, so it cannot share a common factor with any other number. If the array contains 1 and has more than one element, the answer is always false. Failing to check for this edge case will cause incorrect results or infinite loops during factorization.
When the array has only one element, all pairs are trivially traversable (there are no pairs to check). This should return true regardless of the value. Missing this base case leads to incorrect behavior, especially if your factorization or union-find logic assumes at least two elements.
When factorizing numbers, you must completely divide out each prime factor before moving to the next. A common bug is incrementing the factor without fully removing it, causing duplicate unions or missed factors. Use a while loop to divide out each prime completely: while (num % prime == 0) num /= prime.
When using Union-Find with virtual nodes for primes (offset by N), ensure consistent indexing. Array indices run from 0 to N-1, while prime nodes are at positions N + prime. Mixing up these offsets or forgetting to add N when accessing prime nodes causes incorrect unions and false connectivity results.
When the maximum value in the array is large (up to 10^5 or 10^6), allocating arrays sized by the maximum value can consume significant memory. Ensure your sieve and Union-Find structures are appropriately sized as N + MAX + 1 to accommodate both indices and virtual prime nodes without out-of-bounds errors.