题目描述
【leetcode】5178. 四因数( Four Divisors)
给你一个整数数组 nums,请你返回该数组中恰有四个因数的这些整数的各因数之和。
如果数组中不存在满足题意的整数,则返回 0 。
示例:
输入:nums = [21,4,7]
输出:32
解释:
21 有 4 个因数:1, 3, 7, 21
4 有 3 个因数:1, 2, 4
7 有 2 个因数:1, 7
答案仅为 21 的所有因数的和。
提示:
-1 <= nums.length <= 10^4
1 <= nums[i] <= 10^5
第一次解答
思路
暴力法,遍历所有数字,计算每个数字的因数,若因数个数为4个,则把因数和累加到结果中。
在判断数字n的因数有哪些时,我一开始遍历了[1, n]去寻找,这么做得结果是超时。于是我改为遍历[1,
n
\sqrt{n}
n],每找到一个因数i时,n/i也是n的另一个因数,这样就不超时了。不过要注意因数i为
n
\sqrt{n}
n时,n/i ==
n
\sqrt{n}
n, 不要累加重复的因数。
代码:
class Solution {
public:
unordered_map<int, int> divisors_sum;
int findDivisors(int num){
int count = 0;
if(num <=2)
return 0;
if(divisors_sum.find(num) != divisors_sum.end())
return divisors_sum[num];
divisors_sum[num] = 0;
vector<int> divisors;
divisors.reserve(5);
// 为了避免超时,改为判断 i < sqrt(num)
// for(int i=1; i*i<=num; ++i){
for(int i=1; i*i<=num; ++i){
if(num % i == 0){
divisors.push_back(i);
count++;
// 注意 因子 sqrt(num)不要添加两次
if(i*i != num){
divisors.push_back(num / i);
count++;
}
// 若因数个数大于4个,提前退出
if(count > 4)
break;
}
}
if(count == 4){
divisors_sum[num] += divisors[0] + divisors[1] + divisors[2] +divisors[3];
}
return divisors_sum[num];
}
int sumFourDivisors(vector<int>& nums) {
int sum = 0;
for(int i=0; i<nums.size(); ++i){
sum += findDivisors(nums[i]);
}
return sum;
}
};
结果:
第二次解答
思路
看了这位的题解,似乎发现了一种暴力法的通用解法,理解这个思路应该可以解决许多用暴力法超时的问题。
因为leetcode执行测试用例耗时是所有用例的执行时间之和,并且执行不同测试用例时,全局变量是保留的,因此一些反复用到的变量完全可以一次暴力计算好所有情况
放到全局变量中,后续过程就是查找这个全局变量过程了。
当然,为了避免第一次暴力计算所有情况时导致超时,这里也要有一定技巧。作者的骚操作是遍历范围内每个因数,找到范围内每个能被因数整除的数,这么做之所以快,是因为对于因数i,能被它整除的数为i, 2*i, 3*i, .... , k*i
。每一次查找是跳跃i个单位的,所以会减少很多不必要的计算。
代码
代码直接拷贝作者的,我就懒得写了。
bool flag = false;
unordered_map<int, unordered_set<int>> eleDict;
void init() {
for(int i = 1; i <= 1e5; i++) {
for(int j = i; j <= 1e5; j += i) {
eleDict[j].insert(i);
}
}
}
class Solution {
public:
int sumFourDivisors(vector<int>& nums) {
if(flag == false) {
init();
flag = true;
}
int sum = 0;
for(auto it = nums.cbegin(); it != nums.cend(); ++it) {
const unordered_set<int> &eles = eleDict[*it];
if(eles.size() == 4) {
for(auto pit = eles.cbegin(); pit != eles.cend(); pit++) {
sum += *pit;
}
}
}
return sum;
}
};
结果
结果有点慢,但是没有提示超时,在比赛中也顾不了那么多了。
第三次解答
思路
看到解答二这么慢,我感觉查找因数的方法可能还是解答一的快。于是依然是解答二的思路,提前计算出所有结果,但是采取的策略是解答1的策略,从i遍历到
n
\sqrt{n}
n来生成查找表。
代码
bool flag = false;
unordered_map<int, unordered_set<int>> eleDict;
void init() {
for(int i = 1; i <= 1e5; i++) {
for(int j = 1; j*j <= i; j ++) {
if(i % j == 0){
eleDict[i].insert(j);
eleDict[i].insert(i / j);
// 因数若大于4个,提前退出
if(eleDict[i].size() > 4)
break;
}
}
}
}
class Solution {
public:
int sumFourDivisors(vector<int>& nums) {
if(flag == false) {
init();
flag = true;
}
int sum = 0;
for(auto it = nums.cbegin(); it != nums.cend(); ++it) {
const unordered_set<int> &eles = eleDict[*it];
if(eles.size() == 4) {
for(auto pit = eles.cbegin(); pit != eles.cend(); pit++) {
sum += *pit;
}
}
}
return sum;
}
};
结果
可以看到,虽然仍然是暴力法提前计算范围内所有结果,但是由于减少了很多不必要的计算,比解答二还是快了许多。