【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}],每找到一個因數i時,n/i也是n的另一個因數,這樣就不超時了。不過要注意因數i爲n\sqrt{n}時,n/i == n\sqrt{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}來生成查找表。

代碼

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

結果
在這裏插入圖片描述
可以看到,雖然仍然是暴力法提前計算範圍內所有結果,但是由於減少了很多不必要的計算,比解答二還是快了許多。

相關/參考鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章