今日头条2018校园招聘第一次笔试第二题“字符串拼接”题解(一维动态规划及递归解法)

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上发博客,不太熟悉排版,请见谅。如有错误,欢迎讨论。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值