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 = [2,3,6]
Output: true
Explanation:
In this example, there are 3 possible pairs of indices: (0, 1), (0, 2), and (1, 2).
To go from index 0 to index 1, we can use the sequence of traversals 0 -> 2 -> 1, where we move from index 0 to index 2 because gcd(nums[0], nums[2]) = gcd(2, 6) = 2 > 1, and then move from index 2 to index 1 because gcd(nums[2], nums[1]) = gcd(6, 3) = 3 > 1.
To go from index 0 to index 2, we can just go directly because gcd(nums[0], nums[2]) = gcd(2, 6) = 2 > 1. Likewise, to go from index 1 to index 2, we can just go directly because gcd(nums[1], nums[2]) = gcd(3, 6) = 3 > 1.
Example 2:
Input: nums = [3,9,5]
Output: false
Explanation: No sequence of traversals can take us from index 0 to index 2 in this example. So, we return false.
Example 3:
Input: nums = [4,3,12,8]
Output: true
Explanation:
There are 6 possible pairs of indices to traverse between: (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), and (2, 3). A valid sequence of traversals exists for each pair, so we return true.
Constraints:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
A brute force solution would involve checking every possible path between each pair of indices (i, j) to see if a valid traversal exists. This would likely involve a Depth-First Search (DFS) or Breadth-First Search (BFS) for each pair.
false
.true
.import math
def can_traverse_naive(nums):
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
def has_path(start, end, visited):
if start == end:
return True
visited[start] = True
for i in range(len(nums)):
if not visited[i] and gcd(nums[start], nums[i]) > 1:
if has_path(i, end, visited):
return True
return False
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
visited = [False] * len(nums)
if not has_path(i, j, visited):
return False
return True
O(N^2 * (N + E)), where N is the number of elements in nums
and E is the number of edges (pairs with GCD > 1). In the worst case, for each of the N^2 pairs, we might have to explore all N nodes and E edges.
O(N) due to the recursion depth of DFS and the visited
array.
A more efficient solution uses the Union-Find (Disjoint Set Union) data structure. The idea is to connect indices i
and j
if gcd(nums[i], nums[j]) > 1
. After processing all such pairs, we check if all indices are connected, meaning there's a path between any two indices.
n
elements, where n
is the length of nums
.gcd(nums[i], nums[j]) > 1
, union indices i and j in the Union-Find.find(0) == find(i)
for all i from 1 to n-1.true
if all elements are in the same component, and false
otherwise.However, calculating GCD for all pairs of indices is still O(N^2) in the worst case. Instead, we can iterate through each number, find its prime factors, and union the indices that share a prime factor. This dramatically reduces the runtime complexity.
n
elements, where n
is the length of nums
.Map<Integer, Integer>
to store the first occurrence index for each prime factor.nums[i]
:
nums[i]
.i
.i
with the index stored in the Map.import math
def can_traverse(nums):
n = len(nums)
parent = list(range(n))
def find(i):
if parent[i] == i:
return i
parent[i] = find(parent[i])
return parent[i]
def union(i, j):
root_i = find(i)
root_j = find(j)
if root_i != root_j:
parent[root_i] = root_j
prime_map = {}
def prime_factors(n):
factors = set()
while n % 2 == 0:
factors.add(2)
n //= 2
for i in range(3, int(math.sqrt(n)) + 1, 2):
while n % i == 0:
factors.add(i)
n //= i
if n > 2:
factors.add(n)
return factors
for i in range(n):
factors = prime_factors(nums[i])
for factor in factors:
if factor in prime_map:
union(i, prime_map[factor])
else:
prime_map[factor] = i
# Check if all nodes are connected
root = find(0)
for i in range(1, n):
if find(i) != root:
return False
return True
O(N * sqrt(M) + N * α(N)), where N is the number of elements in nums
, M is the maximum value in nums
, and α(N) is the inverse Ackermann function, which grows very slowly and can be considered almost constant for practical input sizes. O(N * sqrt(M)) is for finding prime factors of all numbers and O(N * α(N)) is for Union-Find operations.
O(N + P), where N is the number of elements in nums
(for the parent array in Union-Find) and P is the number of distinct prime factors among all numbers in nums
(for the prime_map
).
nums
contains only one element, return true
because there are no pairs to traverse.prime_factors
function efficiently finds prime factors, but very large prime numbers could increase the execution time.