这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战
题目
提示:
- 1 <= nums.length <= 10^5^
1 <= nums[i] <= 30
解析
先做一个推论:
- 如果一系列数的乘积,可以被表示为若干个不同质数的乘积,那么:这一系列数中的每个数分解后得到的都必然是多个不同的质数。
听起来像废话,解释一下:
- 例如给出数组[2~15],那么,如果子数组是一个所谓的好子数组,那么子数组中的数必然满足下列条件:
- 要么本身是质数,要么是由多个不同质数相乘得到的,即:必然包括K个质数,k>=1。
- 这K个质数,和子数组中别的K不能指向任何相同的质数。(例如:5,15就不是所需的一个结果)。
注意到题目给出的数,范围在1~30之间,那么:
- 列出1~30之间,彼此哪些可以组成好数组?
- 对数据做一下统计,按照上面的规律,就可以计算出最终需要的“好数组”数。
附表:
graph LR 2-->3-->5-->7-->11-->13-->17-->19-->23-->29 2-->6 3-->6 2-->10 5-->10 3-->15 5-->15
根据这个取过了就不能再取的逻辑(若干个不同质数),同时知道30以内的质数只有以下10个:
2,3,5,7,11,13,17,19,23,29
因此,可以用10位来表示取了一些数后的状态,即乘积可以分为哪一些质数。
同时,30以内的所有数,也可以按照上面的,预先做一下分解,方便后续的计算:
static Map<Integer,Integer> dic = new HashMap<>(30);
static{
int[] prime = new int[]{2,3,5,7,11,13,17,19,23,29};
for (int i = 2; i <= 30; i++) {
int t = i;
int bit = 0;
boolean det = false;
for (int i1 = 0; i1 < prime.length&&t!=1&&!det; i1++) {
int pr = prime[i1];
if(t%pr==0){
if(t%(pr*pr)==0){
det = true;
}
bit+=1<<i1;
t = t/pr;
}
}
if(!det) dic.put(i,bit);
}
}
那么,计算的步骤该如何求取?
- 我们把取了哪些质数有多少情况存起来,那么在循环某个数的时候,就可以按照情况(多不算,少的补上计算),对对应的数进行计算。
public int numberOfGoodSubsets(int[] nums) {
//代表1-30个数
int[] numCnt = new int[31];
//这里的意思是:我们会将所有的状态保存下来,而所有状态中最大的是toBinary(1111111111)
long[] statusCnt = new long[0b1111111111+1];
//需要初始化statusCnt[0]=1,这样子在做状态计算的时候,数对应的就可以自己计算上去
statusCnt[0] = 1;
//结果
//计算有多少个数
for (int i : nums) numCnt[i]++;
//由于只有10个数,使用状态压缩减少存储空间
for (int curNum = 2; curNum <= 30; curNum++) {
if(numCnt[curNum]==0) continue;
int curStatus = dic.getOrDefault(curNum, -1);
//没有这个数
if (curStatus == -1) continue;
//此时,我们对所有状态进行查找,目的是:为每一个缺少当前状态的值加上当前的数
//根据我们的分析,可以使用状态转移,而这个状态转移,实际上指的是:
// 缺少X个素数的,在出现这X个素数的数(素数组合或者符合条件的合数)之后,同样地可以组合成好数组
//这里遍历顺序实际上无所谓,因为这里实际上是从已有状态中取的,如果相同的数取过了,状态中就已经显示出来了
// 那么无论是对应状态的组合还是当前的数,如果对应位置上有重复,是不可能取的
for (int historyStatus = 0; historyStatus < statusCnt.length; historyStatus++) {
//与条件:两位为1才为1,不为0表示有相同的位,这里表示有相同的素数出现了
if ((curStatus & historyStatus) != 0) continue;
//status | j 索引位置,表示将j整合进状态为status的任意之后,得到的素数状态
statusCnt[curStatus | historyStatus] =
(statusCnt[historyStatus | curStatus] + statusCnt[historyStatus] * numCnt[curNum]) % mask;
}
}
//结束了状态的计算,最后需要对结果进行一个计算
long res = 0;
for (int i = 1; i < statusCnt.length; i++) {
res = (res + statusCnt[i])%mask;
}
//在状态计算结束后,对1进行补充。
// 对于任意位置的1,根据题意选择选或者不选,对最终的结果数组没有影响,但是都是独立的状态,因此都需要计入
for (int i = 0; i < numCnt[1]; i++) {
res = (res * 2) % mask;
}
return (int)res;
}
执行用时:9 ms, 在所有 Java 提交中击败了78.79%的用户
内存消耗:50.5 MB, 在所有 Java 提交中击败了81.82%的用户