小E的倍数关系子集问题| 豆包MarsCode AI刷题

10 阅读3分钟

题目描述

小E想知道一个给定集合中,有多少个子集满足以下条件:

  • 子集内的所有元素数量大于 1。
  • 子集内的所有元素两两之间互为倍数关系。

由于结果可能非常大,输出的结果需要对 109+7109+7 取模。


测试样例

样例1:

输入:n = 5,a = [1, 2, 3, 4, 5]
输出:6

样例2:

输入:n = 6,a = [2, 4, 8, 16, 32, 64]
输出:57

样例3:

输入:n = 4,a = [3, 6, 9, 12]
输出:5

解题思路

根据问题描述,我们需要找到给定集合的所有子集,这些子集满足以下条件:

  1. 子集内至少包含两个元素。
  2. 子集内的所有元素互为倍数关系。

使用动态规划:

  1. 排序:首先对数组进行排序,这样可以确保当我们检查一个元素是否是另一个元素的倍数时,只需要检查它之前的元素。
  2. 动态规划数组定义:定义一个动态规划数组dp,其中dp[i]表示以a[i]为最大元素的符合条件的子集个数。
  3. 初始化:对于每个元素a[i],它自己可以形成一个子集,所以dp[i]至少为1。
  4. 状态转移:对于每个元素a[i],我们需要检查它之前的所有元素a[j]j < i)。如果a[i]a[j]的倍数,那么所有以a[j]为最大元素的子集都可以通过添加a[i]来扩展,形成新的子集。因此,dp[i]应该增加dp[j]
  5. 累加结果:我们需要累加所有dp[i]的值来得到总的子集个数,但要注意,我们只对那些包含多个元素的子集感兴趣。
  6. 减去单元素子集:由于每个元素自己也是一个子集,我们需要从总和中减去这些单元素子集的数量,即减去n
  7. 取模:由于结果可能非常大,我们需要在每一步计算时都对结果取模。

代码及注释

public class Main {
private static final int MOD = 1000000007;
public static int solution(int n, int[] a) {
    //排序
    Arrays.sort(a);
    int[] dp = new int[n];
    int total = 0;
    for (int i = 0; i < n; i++) {
        // 初始化dp[i]为1,因为每个元素自身可以形成一个子集
        dp[i] = 1;  
        // 遍历当前元素之前的所有元素,检查它们是否是当前元素的倍数
        for (int j = 0; j < i; j++) {
            // 如果a[i]是a[j]的倍数,则可以将a[j]的所有子集扩展到a[i]
            if (a[i] % a[j] == 0) {
                // 更新dp[i],加上所有以a[j]为最大元素的子集个数
                dp[i] = (dp[i] + dp[j]) % MOD;
            }
        }
        total = (total + dp[i]) % MOD;
    }
    int result = (total - n + MOD) % MOD;
    return result;
}
public static void main(String[] args) {
    System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}) == 6);
    System.out.println(solution(6, new int[]{2, 4, 8, 16, 32, 64}) == 57);
    System.out.println(solution(4, new int[]{3, 6, 9, 12}) == 5);
    }
}

感悟

  • 排序的妙用:排序是算法中常用的技巧,它可以简化很多问题的处理。在这个问题中,排序使得我们可以通过简单的相邻元素比较来检查条件,而无需复杂的嵌套循环或排序后的索引映射。
  • 双指针技巧:双指针是处理数组和链表问题时的一种高效技巧。通过两个指针的协同移动,可以在一次遍历中解决很多问题,如寻找最长子序列、子数组等。