Greatest Common Divisor Traversal

Updated on 02 June, 2025
Greatest Common Divisor Traversal header image

Problem Statement

In this programming challenge, you are provided with an array of integers where each element is indexed starting from 0. The primary task is to determine if it's possible to traverse between every distinct and ordered pair of indices (i, j) where i < j. The traversal between two indices is valid if the greatest common divisor (gcd) of the elements at these indices is greater than 1. If such a sequence of traversals exists for every pair under the given conditions, the function should return true; otherwise, it should return false.

Examples

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 <= 105
  • 1 <= nums[i] <= 105

Approach and Intuition

The core idea is to visualize the problem as a graph where nodes represent the elements of the array and edges between any two nodes exist if the gcd of the values at those nodes is greater than 1. The problem then reduces to checking if the graph is fully connected in terms of traversal possibilities.

Example 1 Analysis:

  • Input: nums = [2,3,6]
  • Output: true

Here's the breakdown:

  1. Consider possible index pairs: (0,1), (0,2), and (1,2).
  2. For the pair (0, 2): gcd(2, 6) = 2, which is > 1. Thus, a direct edge exists.
  3. For the pair (1, 2): gcd(3, 6) = 3, also > 1. Thus, another direct edge exists.
  4. For the pair (0, 1): No direct edge (gcd(2, 3) = 1); however, you can traverse using 0 -> 2 -> 1 as intermediate steps.

From the observations, you can traverse from any index to any other, hence the answer is true.

Example 2 Analysis:

  • Input: nums = [3,9,5]
  • Output: false

The breakdown demonstrates:

  1. The pair (0, 2): gcd(3, 5) = 1 indicates no direct edge, and no such path exists through any intermediate node (as the only intermediate option is node 1, which does not help in establishing a valid gcd with nodes 0 and 2).
  2. Since just one valid pair traversal (from index 0 to index 2) is not achievable, the output is false.

Example 3 Analysis:

  • Input: nums = [4,3,12,8]
  • Output: true

This larger set illustrates:

  1. The graph efficiently depicts every possible pair having a direct or indirect route that respects the gcd > 1 rule.
  2. Utilizing edges built on valid gcd values, you can determine paths between every pair either directly or through intermediaries.

Thus, the answer is true given the connectivity potential of the graph. The solution would ideally utilize data structures for disjoint sets or graph traversal algorithms like DFS to represent and navigate the possibilities.

Solutions

  • C++
  • Java
  • Python
cpp
class UnionFind {
    vector<int> root;
    vector<int> rank;

public:
    UnionFind(int size) {
        for (int i = 0; i <= size; i++) {
            root.push_back(i);
            rank.push_back(1);
        }
    }

    int findParent(int x) { return root[x] == x ? x : root[x] = findParent(root[x]); }

    void uniteSets(int x, int y) {
        int rootX = findParent(x);
        int rootY = findParent(y);
        if (rootX == rootY) return;
        if (rank[rootX] > rank[rootY]) swap(rootX, rootY);
        root[rootX] = rootY;
        rank[rootY] += rank[rootX];
    }
};

class Solution {
public:
    bool calculatePairs(vector<int>& elements) {
        int limit = 100000;
        int totalElements = elements.size();
        vector<bool> presence(limit + 1, false);
        for (int element : elements) {
            presence[element] = true;
        }

        if (totalElements == 1) return true;
        if (presence[1]) return false;

        vector<int> smallestPrime(limit + 1, 0);
        for (int num = 2; num <= limit; num++) {
            if (smallestPrime[num] == 0) {
                for (int multiple = num; multiple <= limit; multiple += num) {
                    smallestPrime[multiple] = num;
                }
            }
        }

        UnionFind uf(2 * limit + 1);
        for (int value : elements) {
            int current = value;
            while (current > 1) {
                int primeFactor = smallestPrime[current];
                int adjustedRoot = primeFactor + limit;
                if (uf.findParent(adjustedRoot) != uf.findParent(value)) {
                    uf.uniteSets(adjustedRoot, value);
                }
                while (current % primeFactor == 0) {
                    current /= primeFactor;
                }
            }
        }

        int connectedComponents = 0;
        for (int i = 2; i <= limit; i++) {
            if (presence[i] && uf.findParent(i) == i) {
                connectedComponents++;
            }
        }
        return connectedComponents == 1;
    }
};

This solution in C++ aims to determine if all integers in a given list elements are connected through their prime factors, except for the number 1, using a union-find data structure. The approach can effectively handle up to 100,000 different integers.

Key components of the solution:

  • UnionFind Class: Helps in managing the merging of sets and finding the root parent of each element efficiently. It supports operations for finding the parent and uniting two sets, along with path compression and union by rank for optimization.
  • Prime Sieve Setup: A sieve method is used to compute the smallest prime factor for each number up to a given limit (limit), which will be useful in factorization.
  • Factor Connection: For each number in elements, the solution traverses through its prime factors, adjusting roots for mapping numbers to particular slots in the union-find structure. Each unique prime factor connects to its corresponding element.
  • Connected Components Validation: Finally, the algorithm counts the number of connected components to determine if all numbers are connected through their prime factors. The final connectivity is determined based on whether there is only one unique connected component for all the numbers, excluding any isolated '1s'.

Steps in the solution logic:

  1. Initialize the presence array and smallest prime array.
  2. Build the smallest prime array using a sieve-like approach to mark the smallest prime factor for every integer.
  3. For each number in elements, iterate over its prime factors and unify sets in the union-find structure to establish connections.
  4. Count the number of unique connected components in the union-find structure.
  5. Return true if there is exactly one connected component; otherwise, return false.

The solution efficiently utilizes mathematical properties, data structures, and algorithms to solve the problem with a focus on scalability and runtime optimization.

java
class Solution {

    public boolean canReachEveryPair(int[] elements) {
        int MAXIMUM = 100000;
        int elementCount = elements.length;
        boolean[] exists = new boolean[MAXIMUM + 1];
        for (int element : elements) {
            exists[element] = true;
        }

        if (elementCount == 1) {
            return true;
        }
        if (exists[1]) {
            return false;
        }

        int[] primeSieve = new int[MAXIMUM + 1];
        for (int factor = 2; factor <= MAXIMUM; factor++) {
            if (primeSieve[factor] == 0) {
                for (int index = factor; index <= MAXIMUM; index += factor) {
                    primeSieve[index] = factor;
                }
            }
        }

        UnionFind uf = new UnionFind(2 * MAXIMUM + 1);
        for (int num : elements) {
            int current = num;
            while (current > 1) {
                int prime = primeSieve[current];
                int primeIndex = prime + MAXIMUM;
                if (uf.find(primeIndex) != uf.find(num)) {
                    uf.union(primeIndex, num);
                }
                while (current % prime == 0) {
                    current /= prime;
                }
            }
        }

        int distinctRoots = 0;
        for (int i = 2; i <= MAXIMUM; i++) {
            if (exists[i] && uf.find(i) == i) {
                distinctRoots++;
            }
        }
        return distinctRoots == 1;
    }
}

class UnionFind {

    public int[] parent;
    public int[] rank;

    public UnionFind(int size) {
        parent = new int[size + 1];
        rank = new int[size + 1];
        for (int i = 0; i <= size; i++) {
            parent[i] = i;
            rank[i] = 1;
        }
    }

    public int find(int node) {
        return parent[node] == node ? node : (parent[node] = find(parent[node]));
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                int temp = rootX;
                rootX = rootY;
                rootY = temp;
            }
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }
}

The provided Java solution solves the problem of checking if one can reach every pair of elements in an array, given that each connection between elements is only valid if they share a common prime factor.

  • Initialize a boolean array exists to track which numbers are present in the array elements.
  • Prepare a primeSieve to identify the smallest prime factors of numbers up to a defined MAXIMUM. Utilize a sieve technique to populate the array which assists in factorization later.
  • Utilize a UnionFind (disjoint set union or DSU) structure to efficiently manage and find connected components in a dynamic set of elements. This DSU helps in grouping elements that can be connected directly or transitively through shared prime factors.
  • Traverse the elements and, for each element, factorize it using primes obtained from the primeSieve. Union these elements in the UnionFind structure using their prime factors as intermediates. This way, all numbers sharing a prime factor are grouped together.
  • Finally, check the root parents from the DSU to determine how many distinct connected components exist. If only one distinct set exists, it implies all elements can be reached from any other via shared prime factors, returning true. If more than one distinct group is found, return false.

The function returns if it is possible to traverse between each pair of the array elements using the common prime factor rule, leveraging efficient algorithms for prime factorization and union-find operations for set management.

python
class Solution:

    def checkTraversable(self, numbers):
        maxValue = max(numbers)
        arrayLen = len(numbers)
        visited = [False] * (maxValue + 1)
        for num in numbers:
            visited[num] = True

        if arrayLen == 1:
            return True
        if visited[1]:
            return False

        primeFactors = [0] * (maxValue + 1)
        for i in range(2, maxValue + 1):
            if primeFactors[i] == 0:
                for j in range(i, maxValue + 1, i):
                    primeFactors[j] = i

        unionFind = UnionFind(2 * maxValue + 1)
        for number in numbers:
            current = number
            while current > 1:
                prime = primeFactors[current]
                rootIdx = prime + maxValue
                if unionFind.locate(rootIdx) != unionFind.locate(number):
                    unionFind.union(rootIdx, number)
                while current % prime == 0:
                    current //= prime

        counter = 0
        for k in range(2, maxValue + 1):
            if visited[k] and unionFind.locate(k) == k:
                counter += 1
        return counter == 1


class UnionFind:

    def __init__(self, size):
        self.parent = list(range(size + 1))
        self.rank = [1] * (size + 1)

    def locate(self, vertex):
        if self.parent[vertex] != vertex:
            self.parent[vertex] = self.locate(self.parent[vertex])
        return self.parent[vertex]

    def union(self, vertex1, vertex2):
        root1 = self.locate(vertex1)
        root2 = self.locate(vertex2)
        if root1 != root2:
            if self.rank[root1] > self.rank[root2]:
                root1, root2 = root2, root1
            self.parent[root1] = root2
            self.rank[root2] += self.rank[root1]

The Python script provides a solution to determine if all elements in a list can be traversed using a Greatest Common Divisor (GCD) approach. The method checkTraversable in the Solution class implements an algorithm that uses the Union-Find data structure to handle disjoint sets, which is crucial in determining the connectivity of numbers based on their prime factors.

Here is a step-by-step breakdown of the solution:

  1. Initialize an array visited to keep track of each integer presence in the list and compute the maximum value for array size setup.
  2. Utilize the visited array to handle edge cases such as single-element lists or presence of '1' which should return False.
  3. Compute prime factors for each number up to the maxValue using a sieve approach to optimization in the primeFactors array.
  4. Set up an instance of the UnionFind class, which supports operations to find the root of an element and union two sets, to manage relationships between numbers and their prime factors.
  5. Iterate through each number in the input list, and for each number, map it to a unique index corresponding to its highest prime factor using UnionFind structure.
  6. Reduce the number by its highest prime factor repetitively and union it in the UnionFind structure until all factors are processed.
  7. Finally, count the sets formed by checking the root parent for numbers using the locate method of UnionFind, managing connected components.

The UnionFind class methods locate and union provide efficient path compression and union by rank heuristics respectively, optimizing the process of determining and merging disjoint sets.

This solution efficiently traverses through the numbers, using their prime factorizations to determine if the input array can be traversed completely by GCD-based moves. The use of a disjoint set (UnionFind) is key in mapping out the connectivity or isolation of components defined by these number relations.

Comments

No comments yet.