題目描述
【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, ],每找到一個因數i時,n/i也是n的另一個因數,這樣就不超時了。不過要注意因數i爲時,n/i == , 不要累加重複的因數。
代碼:
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遍歷到 來生成查找表。
代碼
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;
}
};
結果
可以看到,雖然仍然是暴力法提前計算範圍內所有結果,但是由於減少了很多不必要的計算,比解答二還是快了許多。