leetcode
leetcode 2701 ~ 2750
数组的最大公因数排序

数组的最大公因数排序

难度:

标签:

题目描述

You are given an integer array nums, and you can perform the following operation any number of times on nums:

  • Swap the positions of two elements nums[i] and nums[j] if gcd(nums[i], nums[j]) > 1 where gcd(nums[i], nums[j]) is the greatest common divisor of nums[i] and nums[j].

Return true if it is possible to sort nums in non-decreasing order using the above swap method, or false otherwise.

 

Example 1:

Input: nums = [7,21,3]
Output: true
Explanation: We can sort [7,21,3] by performing the following operations:
- Swap 7 and 21 because gcd(7,21) = 7. nums = [21,7,3]
- Swap 21 and 3 because gcd(21,3) = 3. nums = [3,7,21]

Example 2:

Input: nums = [5,2,6,2]
Output: false
Explanation: It is impossible to sort the array because 5 cannot be swapped with any other element.

Example 3:

Input: nums = [10,5,9,3,15]
Output: true
We can sort [10,5,9,3,15] by performing the following operations:
- Swap 10 and 15 because gcd(10,15) = 5. nums = [15,5,9,3,10]
- Swap 15 and 3 because gcd(15,3) = 3. nums = [3,5,9,15,10]
- Swap 10 and 15 because gcd(10,15) = 5. nums = [3,5,9,10,15]

 

Constraints:

  • 1 <= nums.length <= 3 * 104
  • 2 <= nums[i] <= 105

代码结果

运行时间: 494 ms, 内存: 55.5 MB


/*
 * Solution approach using Java Streams:
 * 1. Create a sorted copy of the array, sortedNums.
 * 2. Identify the components of connected elements using Union-Find by calculating gcd for each pair.
 * 3. Use Stream operations to handle arrays and collections.
 */

import java.util.*;
import java.util.stream.*;

public class SolutionStream {
    private int gcd(int a, int b) {
        while (b != 0) {
            int temp = b;
            b = a % b;
            a = temp;
        }
        return a;
    }

    public boolean canSortByGCD(int[] nums) {
        int[] sortedNums = Arrays.stream(nums).sorted().toArray();
        UnionFind uf = new UnionFind(100001);

        IntStream.range(0, nums.length).forEach(i ->
            IntStream.range(i + 1, nums.length).forEach(j -> {
                if (gcd(nums[i], nums[j]) > 1) {
                    uf.union(nums[i], nums[j]);
                }
            })
        );

        return IntStream.range(0, nums.length).allMatch(i ->
            uf.find(nums[i]) == uf.find(sortedNums[i])
        );
    }

    class UnionFind {
        private int[] parent;

        public UnionFind(int size) {
            parent = new int[size];
            Arrays.setAll(parent, i -> i);
        }

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

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

解释

方法:

本题解通过构建一个联合查找数据结构来解决问题。主要步骤包括初始化联合查找数组,筛选出所有的素数并将数组中的数与其最小的素因子进行连接。首先,定义每个数自身为其父节点。接着,通过埃拉托斯特尼筛法找出所有小于最大数+1的素数,并对于每个素数,遍历其倍数,如果倍数在数组中,将这个倍数与其最小素因子连接起来,形成一个联合查找组。最后,将原数组排序并比较原数组的每个元素与排序后数组的每个元素是否属于同一个联合查找组。如果所有元素都属于同一个组,说明可以通过交换使数组有序。

时间复杂度:

O(m log m + n log log n)

空间复杂度:

O(n + m)

代码细节讲解

🦆
在题解中,为何首先选择使用联合查找数据结构来解决这个问题?
联合查找数据结构(也称为并查集)非常适合处理元素分组和组成员关系查询的问题,尤其是在需要频繁合并集合和查询集合归属的场景中。在这个问题中,需要判断数组是否可以通过交换变为有序状态,这涉及到判断数组中任意两个元素是否可以通过一系列交换到达彼此的位置。联合查找数据结构可以动态地合并任意两个元素的集合,并快速查询两个元素是否属于同一个集合,从而有效地解决问题。
🦆
如何确保通过埃拉托斯特尼筛法筛选出的素数是`nums`数组中的数的有效因子?
埃拉托斯特尼筛法在这里用于生成小于等于数组中最大数`max(nums)`的所有素数。随后,对于每个筛选出的素数,通过遍历其倍数来检查这些倍数是否在数组`nums`中存在。如果存在,则这个倍数显然可以被当前素数整除,因此确认了该素数是此倍数的一个有效因子。这样,可以确保所有在数组中的数与其素数因子之间的关系被正确建立,从而在并查集中正确地连接它们。
🦆
在联合查找数据结构中,`union`函数是如何确保只连接数组`nums`中实际存在的数的素因子?
在实现`union`函数之前,算法首先通过筛法找到所有可能的素数,然后遍历这些素数的倍数。在遍历倍数时,检查每个倍数是否在输入数组`nums`中。这是通过将数组元素存储在一个集合`st`中并使用`if q in st`来检查实现的。只有当倍数`q`确实在数组`nums`中时,`union`函数才会被调用来连接这个数`q`和它的素因子`p`。这样确保了`union`操作只在数组中实际存在的数及其素因子之间进行,从而维护了并查集的准确性和效率。

相关问题