3.24晚的笔试,结束后题目看不到了,有人截图了,来源:https://www.jianshu.com/p/00d3fd1d9e23
最新更新:在leetcode 上有一道类似的题,区别在于第一种操作没有s = s + s。leetcode上这道题可以看作是找到一个数n所有的素数因子之和;头条这题类似,是找到一个数n所有素数因子之和,再减去因子的个数。那么可以用动规,也可以递归。
下面给出递归的代码,非常简洁;
int recursion(int n) {
if (n == 1) return 0;
for (int i = 2; i <= int(sqrt(n)); i++)
if (n % i == 0) return i - 1 + recursion(n / i);
return n - 1;
}
然后我又想到了一个可以优化的地方,就是每次递归函数里的i只要从上一个因子开始找就行,改进后的代码:
int recursion(int n, int p) {
if (n == 1) return 0;
for (int i = p; i <= int(sqrt(n)); i++)
if (n % i == 0) return i - 1 + recursion(n / i, i);
return n - 1;
}
由于看到很多人给出的都是广度优先搜索(BFS)的解法,或者是二维动态规划的解法;前者是指数时间的算法,后者时间、空间复杂度均为O(n^2)。本文提出一种多项式复杂度解法,时间复杂度O(n^(1.5)),空间复杂度O(n)的一维动态规划。
递推公式:dp[i] = p - 1 + dp[(i / p)]; p 是从小到大第一个能整除i的数(从2开始找,实际上只要找素数就行)。
int DP(int n) {
vector<int> dp(n+1, 0);
dp[1] = 0;
for (int i = 2; i <= n; i++) {
for (int j = 2; j <= int(sqrt(i)); j++) {
if (i % j == 0) {
dp[i] = j - 1 + dp[i/j];
break;
}
}
if (dp[i] == 0) dp[i] = i - 1;
}
return dp[n];
}
实际上上面的代码可以优化的地方:先计算出1~sqrt(n)范围内的所有素数;然后只要在这些素数里找到第一个符合条件(整除)的素数就行。所以平均的时间复杂度几乎是线性的。
下面是优化后的代码:
int DP(int n) {
vector<int> prime;
for (int i = 2; i <= int(sqrt(n)); i++) {
int flag = true;
for (int j = 2; j <= int(sqrt(i)); j++) {
if (i % j == 0) { flag = false; break; }
}
if (flag) prime.emplace_back(i);
}
vector<int> dp(n+1, 0);
dp[1] = 0;
for (int i = 2; i <= n; i++) {
for (int j = 0; j < prime.size() && prime[j] <= int(sqrt(i)); j++) {
if (i % prime[j] == 0) {
dp[i] = prime[j] - 1 + dp[i/prime[j]];
break;
}
}
if (dp[i] == 0) dp[i] = i - 1;
}
return dp[n];
}
测试了一下性能,当n为1000000时,也只需要0.25s的时间;
第一次在CSDN上发博客,不太熟悉排版,请见谅。如有错误,欢迎讨论。