题目描述
小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
解题思路
根据问题描述,我们需要找到给定集合的所有子集,这些子集满足以下条件:
- 子集内至少包含两个元素。
- 子集内的所有元素互为倍数关系。
使用动态规划:
- 排序:首先对数组进行排序,这样可以确保当我们检查一个元素是否是另一个元素的倍数时,只需要检查它之前的元素。
- 动态规划数组定义:定义一个动态规划数组
dp
,其中dp[i]
表示以a[i]
为最大元素的符合条件的子集个数。 - 初始化:对于每个元素
a[i]
,它自己可以形成一个子集,所以dp[i]
至少为1。 - 状态转移:对于每个元素
a[i]
,我们需要检查它之前的所有元素a[j]
(j < i
)。如果a[i]
是a[j]
的倍数,那么所有以a[j]
为最大元素的子集都可以通过添加a[i]
来扩展,形成新的子集。因此,dp[i]
应该增加dp[j]
。 - 累加结果:我们需要累加所有
dp[i]
的值来得到总的子集个数,但要注意,我们只对那些包含多个元素的子集感兴趣。 - 减去单元素子集:由于每个元素自己也是一个子集,我们需要从总和中减去这些单元素子集的数量,即减去
n
。 - 取模:由于结果可能非常大,我们需要在每一步计算时都对结果取模。
代码及注释
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);
}
}
感悟
- 排序的妙用:排序是算法中常用的技巧,它可以简化很多问题的处理。在这个问题中,排序使得我们可以通过简单的相邻元素比较来检查条件,而无需复杂的嵌套循环或排序后的索引映射。
- 双指针技巧:双指针是处理数组和链表问题时的一种高效技巧。通过两个指针的协同移动,可以在一次遍历中解决很多问题,如寻找最长子序列、子数组等。