【leetcode】5178. 四因数( Four Divisors)


题目描述

【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;
    }
};

结果
在这里插入图片描述
可以看到,虽然仍然是暴力法提前计算范围内所有结果,但是由于减少了很多不必要的计算,比解答二还是快了许多。

相关/参考链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值