「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」
编程世界总是离不了算法
最近在看框架源码时,会有很多算法的实现逻辑,有时候会感到吃力
于是决定蹭着假期,加强算法和数据结构相关的知识
那怎么提升呢?
其实我知道算法这东西没有捷径,多写多练才能提升,于是我开启我的LeetCode刷题之旅
第一阶段目标是:200道,每天1到2篇
为了不乱,本系列文章目录分为三部分:
- 今日题目:xxx
- 我的思路
- 代码实现
今天题目:1994. 好子集的数目
给你一个整数数组 nums 。如果 nums 的一个子集中,所有元素的乘积可以表示为一个或多个 互不相同的质数 的乘积,那么我们称它为 好子集 。
比方说,如果 nums = [1, 2, 3, 4] : [2, 3] ,[1, 2, 3] 和 [1, 3] 是 好 子集,乘积分别为 6 = 23 ,6 = 23 和 3 = 3 。 [1, 4] 和 [4] 不是 好 子集,因为乘积分别为 4 = 22 和 4 = 22 。 请你返回 nums 中不同的 好 子集的数目对 109 + 7 取余 的结果。
nums 中的 子集 是通过删除 nums 中一些(可能一个都不删除,也可能全部都删除)元素后剩余元素组成的数组。如果两个子集删除的下标不同,那么它们被视为不同的子集。
示例 1:
输入:nums = [1,2,3,4] 输出:6 解释:好子集为:
- [1,2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [1,2,3]:乘积为 6 ,可以表示为互不相同的质数 2 和 3 的乘积。
- [1,3]:乘积为 3 ,可以表示为质数 3 的乘积。
- [2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6 ,可以表示为互不相同的质数 2 和 3 的乘积。
- [3]:乘积为 3 ,可以表示为质数 3 的乘积。 示例 2:
输入:nums = [4,2,3,15] 输出:5 解释:好子集为:
- [2]:乘积为 2 ,可以表示为质数 2 的乘积。
- [2,3]:乘积为 6 ,可以表示为互不相同质数 2 和 3 的乘积。
- [2,15]:乘积为 30 ,可以表示为互不相同质数 2,3 和 5 的乘积。
- [3]:乘积为 3 ,可以表示为质数 3 的乘积。
- [15]:乘积为 15 ,可以表示为互不相同质数 3 和 5 的乘积。
提示:
1 <= nums.length <= 105 1 <= nums[i] <= 30
我的思路
如何判断一个子集是否好子集
暴力法:对每个元素进行分解质因数,然后再判断有没重复的质因数。
预先分解质因数,可以避免重复计算,此时每个数字能得到一个质因数集合。
数据范围是 1 <= nums[i] <= 30, 因此可以将质因数集合压缩到一个 int 内,每一个二进制位代表该质数是否出现,例如 10 = 2 * 5, 质因数集合可用二进制数 10010 表示。将多个集合进行与运算便可得知是否存在重复质因数。
因为 30 以内的质数只有 10 个,可以进一步压缩质因数集合,此时(从右到左)第 i 个二进制位代表第 i 个质数是否出现,例如 21 = 3 * 7,可用二进制数 1010 表示。此压缩方式不必须,但是可以容纳更大的输入范围。
遍历所有的好子集
要拆分为互不相同的质数的乘积,除了 1, 必然不存在重复数字,因此可以统计出现次数,合并重复数字。 使用回溯法,从 2 到 30, 尝试所有数字的组合,每个数字都有选取和不选取两条分支。
统计好子集的数量
选取某个数字时,可以选取每一个出现该数字的位置,因此有出现次数种可能性。
将该子集各个数字的可能性相乘即可得到该子集的数量。
部分数字,例如 4 = 2 * 2, 必然不会选取,可以先排除。
计数为 0 或质因数集合有重复时,可排除选取分支。
需要减去不选取任何数字的情况。
每个 1 可提供两种可能性。
代码实现
/**
* @param {number[]} nums
* @return {number}
*/
var numberOfGoodSubsets = function (nums) {
// 可以组合的数
const sets = [2, 3, 5, 6, 7, 10, 11, 13, 14, 15, 17, 19, 21, 22, 23, 26, 29, 30];
// 代表质数乘积的状态位
const bits = [1, 2, 4, 3, 8, 5, 16, 32, 9, 6, 64, 128, 10, 17, 256, 33, 512, 7];
const mod = 1000000007;
const freq = new Array(31).fill(0);
for (const n of nums) freq[n]++;
// 对每个可以组合的数进行遍历
let [acc, state, count] = [-1, 0, 1];
(function backtrack(i) {
// 得到一种组合方式,统计数量
if (i >= sets.length) return acc = (acc + count) % mod;
// 不取这个数
backtrack(i + 1);
const [n, b] = [freq[sets[i]], bits[i]];
// 计数为 0, 跳过
if (!n) return;
// 无法选取, 跳过
if (state & b) return;
// 选取时提供 n 种可能性,记录状态
let temp = count;
[count, state] = [count * n % mod, state | b];
// 递归
backtrack(i + 1);
// 回溯,还原状态
[count, state] = [temp, state ^ b];
})(0);
// 每个 1 提供取与不取两种可能
while (freq[1]--) acc = (acc * 2) % mod;
return acc;
};
总结
实现方式其实有很多,这里仅供参考~
由于刚开始刷题,也不知道从哪里刷好,如果前辈们有好的建议,希望不吝赐教,感谢🌹