在问 · 算法题解

650. 两个键的键盘

中等动态规划动态规划(按最小因子转移)力扣原题 ↗

题目描述

记事本里一开始只有一个字符 'A'。每一步可以做以下两种操作之一。

Copy All:把记事本当前的全部内容复制到剪贴板,不能只复制一部分。Paste:把剪贴板里上一次复制的内容追加到记事本末尾。

输入整数 n,返回让记事本里恰好出现 n 个 'A' 所需的最少操作次数。

示例 1
输入:n = 3
输出:3

解释:先复制一次,再粘贴两次,共 3 步得到 "AAA"。

示例 2
输入:n = 1
输出:0

解释:一开始就有 1 个 'A',不需要操作。

数据范围

  • 1 <= n <= 1000

解题思路:动态规划(按最小因子转移)

想得到 i 个 A,最后一次复制时屏上有 d 个(d 必须整除 i),然后复制 1 次、粘贴 i/d - 1 次,共 i/d 步,故 dp[i] = min(dp[d] + i/d)。可以证明,取 d = i / p(p 为 i 的最小质因子)即最优,所以只需从 2 开始找第一个因子 j:dp[i] = dp[i / j] + j;找不到(i 是质数)就是 dp[i] = i,即复制一次、粘贴 i - 1 次。最终 dp[n] 等于 n 的质因子之和。

为什么正确

计算 dp[i] 时,所有小于 i 的 dp 都已算好。对于 a, b >= 2 总有 a + b <= a · b,所以拆成质因子不会更差。

复杂度分析

时间 O(n√n),空间 O(n)。

易错点

n = 1 时答案是 0,不需要任何操作。不能只复制一部分字符,所以最后一次复制时的数量必须整除 n。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def minSteps(self, n: int) -> int:
        dp = [0] * (n + 1)
        for i in range(2, n + 1):
            dp[i] = i
            for j in range(2, int(i ** 0.5) + 1):
                if i % j == 0:
                    dp[i] = dp[i // j] + j
                    break
        return dp[n]

载入编辑器,自己跑一遍 →

其他解法

动态规划逐个长度求最少操作数,完全机械;分解质因数先用数学证明答案是质因数之和,再直接计算,更快也更省空间。

解法 2:分解质因数 时间 O(√n),空间 O(1)

一次“复制全部 + 粘贴 p − 1 次”用 p 步把长度乘以 p。得到 n 就是把 n 分解成若干个因子之积,总步数是这些因子之和。当 a, b ≥ 2 时 a + b ≤ a·b,合数因子拆开不会变差,所以答案就是 n 的质因数之和(计重数)。用试除法从 2 开始分解即可。

只需试除到 √n,空间常数。缺点是要先证明“拆成质因数最优”这一结论。

class Solution:
    def minSteps(self, n: int) -> int:
        ans = 0
        p = 2
        while p * p <= n:
            while n % p == 0:
                ans += p
                n //= p
            p += 1
        if n > 1:
            ans += n
        return ans

动态规划 专题的其他题目

查看 动态规划 全部题目 →