leetcode
leetcode 2351 ~ 2400
最大公约数遍历

最大公约数遍历

难度:

标签:

题目描述

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

代码结果

运行时间: 70 ms, 内存: 29.7 MB


/*
 * 思路:
 * 使用 Java Stream 的方式来处理 gcdMap 的生成,并使用并查集进行连通性检查。
 */
import java.util.*;
import java.util.stream.*;

public class Solution {
    public boolean canTraverseAllPairs(int[] nums) {
        int n = nums.length;
        UnionFind uf = new UnionFind(n);
        Map<Integer, List<Integer>> gcdMap = IntStream.range(0, n)
            .boxed()
            .flatMap(i -> getPrimeFactors(nums[i]).stream().map(f -> new AbstractMap.SimpleEntry<>(f, i)))
            .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
        gcdMap.values().forEach(list -> IntStream.range(1, list.size()).forEach(i -> uf.union(list.get(0), list.get(i))));
        return uf.count() == 1;
    }

    private List<Integer> getPrimeFactors(int num) {
        List<Integer> factors = new ArrayList<>();
        for (int i = 2; i <= Math.sqrt(num); i++) {
            if (num % i == 0) {
                factors.add(i);
                while (num % i == 0) num /= i;
            }
        }
        if (num > 1) factors.add(num);
        return factors;
    }

    class UnionFind {
        private int[] parent, rank;
        private int count;

        public UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            count = n;
            IntStream.range(0, n).forEach(i -> parent[i] = i);
        }

        public int find(int p) {
            if (parent[p] != p) parent[p] = find(parent[p]);
            return parent[p];
        }

        public void union(int p, int q) {
            int rootP = find(p);
            int rootQ = find(q);
            if (rootP != rootQ) {
                if (rank[rootP] > rank[rootQ]) {
                    parent[rootQ] = rootP;
                } else if (rank[rootP] < rank[rootQ]) {
                    parent[rootP] = rootQ;
                } else {
                    parent[rootQ] = rootP;
                    rank[rootP]++;
                }
                count--;
            }
        }

        public int count() {
            return count;
        }
    }
}

解释

方法:

The given solution tries to determine if any pair of indices in the array can be traversed using the greatest common divisor (gcd) condition. It first checks trivial cases such as single-element arrays and arrays containing the number '1'. Afterwards, it removes duplicates and sorts the array in descending order. The code then attempts to check if, for each element, there exists another element with a gcd greater than 1. If it finds such a pair, it modifies the second element by multiplying it with the first, presumably to increase future gcd values artificially. If it cannot find any such pair for any element, it returns False, otherwise True if all elements can be paired up successfully.

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在算法中要先去除数组中的重复元素并进行排序?
去除重复元素是为了简化问题,避免重复计算和处理相同元素对最大公约数的影响,从而提高算法效率。排序则是为了让数组中的元素按照一定顺序排列,这样在遍历寻找满足条件的元素对时,可以更快地确定是否存在有效的最大公约数对。特别是在降序排序后,可以优先处理较大的数,这有助于快速找到满足条件的数对,因为较大的数更有可能与其它数有较大的最大公约数。
🦆
在解题思路中提到,如果数组中存在数字1,就直接返回False。请问这样做的理由是什么?
在这种算法中,一旦数组中存在数字1,算法需要判定能否通过最大公约数连接任意一对数字。但由于1与任何数的最大公约数都是1,这意味着1不能与任何其他数形成最大公约数大于1的对。因此,如果数组中含有1,就无法满足题目要求的遍历所有数对的条件,直接返回False是一个有效的提前终止判断,节省了不必要的计算。
🦆
算法中提到将满足条件的第二个元素乘以第一个元素,这样做的目的和效果是什么?
在算法中修改第二个元素(nums[j])乘以第一个元素(nums[i])的目的在于尝试人为地增加数组中元素的值,以此增加后续可能的最大公约数计算值。这种改变基于假设通过增加数值大小可能会产生更多的公约数,进而有更多机会找到满足条件的数对。然而,这种做法并不总是有效,并且可能导致算法的复杂性增加,因为修改元素值也可能破坏原有的最大公约数关系,使问题更加复杂。
🦆
对于修改数组元素的操作,为什么只修改了`nums[j]`而没有修改`nums[i]`?这样的操作是否会影响算法的正确性?
在这个算法中,只修改`nums[j]`而不修改`nums[i]`主要是因为在遍历过程中,`nums[i]`作为起始参照点,如果修改了`nums[i]`,将影响后续所有的比较和计算,增加处理的复杂度。而修改`nums[j]`则是在找到满足条件的一对后,尝试改变其值以便后续操作可能利用新的值生成更大的最大公约数。这种修改虽然可以在某些情况下帮助找到解决方案,但实际上可能会降低算法的准确性和预测性,因为它改变了数组的自然组成,可能导致算法无法正确判断所有情况。

相关问题