先从leetCode一个题目开始
leetcode原题:四因数
按照题目的意思我们可以首先枚举出数组最大值范围内所有数字的全部因数,找出其中因数为4的元素,并将其因数累加。
暴力法
暴力法就是通过枚举每个元素的因数个数,并判断其因数个数是否等于4,如果等于4,我们则将其累加,如果一个元素num 包括因数i,那么它也必然包括因数num / i。因此我们只需要判断到 √num即可。
代码如下
class Solution {
public int sumFourDivisors(int[] nums) {
int ans = 0;
for (int num : nums) {
// factor_cnt: 因数的个数
// factor_sum: 因数的和
int factor_cnt = 0, factor_sum = 0;
for (int i = 1; i * i <= num; ++i) {
if (num % i == 0) {
++factor_cnt;
factor_sum += i;
if (i * i != num) {
// 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
++factor_cnt;
factor_sum += num / i;
}
}
}
if (factor_cnt == 4) {
ans += factor_sum;
}
}
return ans;
}
}
数学方法处理
我们可以观察到在一定范围内的整数中,因数为4的元素不会很多,所以我们可以通过预处理的方法将该范围内的因数个数为4的元素处理出来,至于要如何处理出来,我们可以运用到一些数学知识。
算数基本定理
算术基本定理可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积N=P1^a1 * P2^a2 * P3^a3 … Pn^an,这里P1<P2<P3…<Pn均为质数,其中指数ai是正整数。
质数筛选法
埃拉托斯特尼筛法:要得到自然数n以内的全部素数,必须把不大于 √num的所有素数的倍数剔除,剩下的就是素数。 给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。
转换成代码形式
List<Integer> primes = new ArrayList<Integer>();
boolean[] isPrime = new boolean[100001];
Arrays.fill(isPrime, true);
for (int i = 2; i <= C; ++i) {
if (isPrime[i]) {
primes.add(i);
}
for (int j = i + i; j <= C; j += i) {
isPrime[j] = false;
}
}
欧拉筛法:欧拉筛法和埃式筛法形式上类似,假设要筛出n以内的素数。我们先把所有数标记为素数。枚举i从2到n,所以因为i是从小到大的,如果i没有被前面的数(比它小的数)标记为合数,则将其加入素数列表。现在用i来筛后面的数,枚举已经筛出来的素数prime[j](j=1~cnt),标记i * prime[j]为合数,当i可以被prime[j]整除时退出循环,i++。欧拉筛法的效率比埃式筛法效率更高,因为对于指定范围内的数据,如果一个合数可以被多个素数整除的话,那么欧拉筛法只会判断一次,而埃式筛法则会多次判断,比如42可以被2,3,7整除,埃式筛法会在素数为2,3,7的时候都乘以一次倍数,而欧拉筛法只会在遇到第一个素数2时就发现42不是素数从而break,跳出循环。
转换成代码形式
List<Integer> primes = new ArrayList<Integer>();
boolean[] isPrime = new boolean[100001];
Arrays.fill(isPrime, true);
for (int i = 2; i <= 100000; ++i) {
if (isPrime[i]) {
primes.add(i);
}
for (int prime : primes) {
if (i * prime > 100000) {
break;
}
isPrime[i * prime] = false;
if (i % prime == 0) {
// 表示i的最小质因数是prime
// 而i*prime则可以被更小的质因数 prime筛掉,因此不需要在遍历了
break;
}
}
}
解题
具体解题思路参考题解
我们先获取nums中因数个数为4的元素,首先我们要获取每个元素的因数个数,根据算数基本定理,一个元素可以分解为多个质因数相乘,该元素的因数个数可以由以下公式得到
,αi为上述质因数的指数,该公式是根据排列组合得到,已知x可以被质因数pi ^ αi整除,那么x的因数就可以包含(0…αi)个PI,那么x的因数个数就为所有情况的排列,即上述公式。根据上述公式x的因数为4的情况有两种,一个是x只有一个质因数,即αi的值为3,或者x有两个不同的质因数。因此我们需要列举题目范围内的所有质因数,并维护一个哈希表factor4,对于质因数立方根在数据范围内的质数和其能构成的因数之和放入哈希表,对于质因数有两个的情况,我们需要两层遍历,将两个质因数所能构成的情况放入哈希表,最后遍历数组,将质因数个数为4的元素的质因数之和取出,累加到ans并返回。
class Solution {
public int sumFourDivisors(int[] nums) {
// C 是数组 nums 元素的上限,C3 是 C 的立方根
int C = 100000, C3 = 46;
boolean[] isPrime = new boolean[C + 1];
Arrays.fill(isPrime, true);
List<Integer> primes = new ArrayList<Integer>();
// 埃拉托斯特尼筛法
for (int i = 2; i <= C; ++i) {
if (isPrime[i]) {
primes.add(i);
}
for (int j = i + i; j <= C; j += i) {
isPrime[j] = false;
}
}
// 通过质数表构造出所有的四因数
Map<Integer, Integer> factor4 = new HashMap<Integer, Integer>();
for (int prime : primes) {
if (prime <= C3) {
factor4.put(prime * prime * prime, 1 + prime + prime * prime + prime * prime * prime);
}
}
for (int i = 0; i < primes.size(); ++i) {
for (int j = i + 1; j < primes.size(); ++j) {
if (primes.get(i) <= C / primes.get(j)) {
factor4.put(primes.get(i) * primes.get(j), 1 + primes.get(i) + primes.get(j) + primes.get(i) * primes.get(j));
} else {
break;
}
}
}
int ans = 0;
for (int num : nums) {
if (factor4.containsKey(num)) {
ans += factor4.get(num);
}
}
return ans;
}
}