动态规划的“数学之魂”:从DP推演到质因数分解——巧解「只有两个键的键盘」

哈喽,各位,我是前端小L。

我们的DP之旅,已经探索了序列、矩阵、树上的各种问题,它们大多是"分析型"的------在一个给定的结构上寻找最优解。今天,我们将再次回到"构造型"DP的世界,我们的任务,是从一个A开始,通过"复制"和"粘贴",以最快的速度,"生产"出nA

这道题,是DP与初等数论结合的绝佳典范。它将向我们展示,动态规划的递推关系,有时会惊人地揭示出问题背后隐藏的数学本质。

力扣 650. 只有两个键的键盘

https://leetcode.cn/problems/2-keys-keyboard/

题目分析:

  • 初始状态 : 屏幕上有1个A

  • 操作:

    1. Copy All: 复制屏幕上所有的A到剪贴板。

    2. Paste: 将剪贴板的内容粘贴到屏幕上。

  • 目标 : 得到恰好 nA,所需要的最少操作次数。

核心洞察: Paste操作的次数,取决于你上一次Copy时屏幕上有多少个A

  • 如果你上次在有 jA时按下了Copy,那么每次Paste,屏幕上的A就会增加 j 个。

  • 要从 jA变成 iA,你需要1次Copy ,和 (i/j - 1)Paste。总共需要 1 + (i/j - 1) = i/j 次操作。

  • 这也意味着,j 必须是 i 的一个因数

思路一:动态规划的"正统"之路 (O(n²))

这个"因数"关系,就是我们构建DP的基石。

1. DP状态定义: dp[i] 表示:得到 iA所需要的最少操作次数。

2. 状态转移方程推导: 为了计算 dp[i],我们思考,这 iA是怎么来的? 它必然是从某个状态 dp[j]ji 的因数)通过CopyPaste得到的。 从 jAiA,需要的操作数是 i/j 次。 所以,通过这条路径得到的总操作数是 dp[j] + i/j

但是,i 可能有很多因数 j。我们应该从哪个 j 转移过来呢? 为了让 dp[i] 最小,我们必须遍历所有 i 的因数 j ,然后取所有 dp[j] + i/j 结果中的最小值。

于是,我们得到了状态转移方程: dp[i] = min_{j | i, j < i} { dp[j] + i/j }

3. Base Case: dp[1] = 0。我们一开始就有1个A,不需要任何操作。

代码实现 (DP)

复制代码
class Solution {
public:
    int minSteps(int n) {
        if (n == 1) return 0;
        vector<int> dp(n + 1);
        dp[1] = 0;

        for (int i = 2; i <= n; ++i) {
            dp[i] = i; // 初始化为最坏情况:从1个A复制,粘贴i-1次,共i步
            // 遍历 i 的所有因数 j (从大到小或从小到大都可以)
            for (int j = i / 2; j >= 1; --j) {
                if (i % j == 0) {
                    dp[i] = min(dp[i], dp[j] + i / j);
                }
            }
        }
        return dp[n];
    }
};

这个解法是正确的,但效率不高。它引导我们走向了更深层次的思考。

思路二:"Aha!"时刻------DP关系揭示的数学本质

让我们仔细审视 dp[i] = dp[j] + i/j。 如果 i = a * b,那么 dp(a*b) = dp(a) + b (假设从a转移)。

现在,让我们考虑 n质因数分解 。 假设 n = p1 * p2 * p3 (p1, p2, p3 都是质数)。

  • dp(n) = dp(p1 * p2 * p3)

  • 为了得到 n,我们可以先得到 p1 * p2,然后再乘以 p3

  • 那么,dp(n) = dp(p1 * p2) + p3

  • 同理,dp(p1 * p2) = dp(p1) + p2

  • dp(p1) 是多少?因为 p1 是质数,它的唯一因数是1。所以 dp(p1) = dp(1) + p1/1 = 0 + p1 = p1

把它们串起来: dp(n) = dp(p1 * p2) + p3 = (dp(p1) + p2) + p3 = p1 + p2 + p3

一个惊人的结论诞生了:得到 nA的最少操作次数,恰好等于 n 的所有质因数之和!

为什么这个策略是最优的?因为CopyPaste的操作,本质上是一种"乘法"增长。而质因数,是构成一个数的最基本的"乘法"单位。我们把 n 分解成最基本的乘法单元,然后一步步地"乘"上去,这自然就是最经济的路径。

最终解法:质因数分解 (O(sqrt(n)))

现在,问题被彻底"降维"成了一个纯粹的数学问题:求 n 的质因数之和。

算法流程:

  1. 初始化 ans = 0

  2. i = 2 开始循环,i*i <= n: a. 如果 n 能被 i 整除,说明 i 是一个质因数。 b. 我们就把 i 加到 ans 中,然后把 n 除以 i,继续检查 i 是否还是 n 的因数。

  3. 循环结束后,如果 n > 1,说明 n 本身就是一个大的质因数,把它也加到 ans 中。

代码实现 (数学解法)

复制代码
class Solution {
public:
    int minSteps(int n) {
        int ans = 0;
        for (int i = 2; i * i <= n; ++i) {
            while (n % i == 0) {
                ans += i;
                n /= i;
            }
        }
        if (n > 1) {
            ans += n;
        }
        return ans;
    }
};

总结:DP是发现规律的"显微镜"

今天这道题,是DP学习之旅中一次极其深刻和美妙的体验。它向我们展示了:

动态规划,不仅是一种求解工具,更是一种强大的分析工具。它能像一台"显微镜",帮助我们洞察问题内部的结构和规律,有时甚至能将一个复杂的DP问题,指引向一个更简单、更本质的数学解法。

咱们下期见~

相关推荐
RTC老炮3 小时前
webrtc弱网-ReceiveSideCongestionController类源码分析及算法原理
网络·算法·webrtc
21号 13 小时前
9.Redis 集群(重在理解)
数据库·redis·算法
hadage2335 小时前
--- 数据结构 AVL树 ---
数据结构·算法
liu****5 小时前
8.list的使用
数据结构·c++·算法·list
阿拉丁的梦5 小时前
后期材质-屏幕冲击径向模糊
算法·材质
weixin_429630266 小时前
实验二-决策树-葡萄酒
算法·决策树·机器学习
茉莉玫瑰花茶6 小时前
floodfill 算法(dfs)
算法·深度优先
CoderCodingNo7 小时前
【GESP】C++五级考试大纲知识点梳理, (5) 算法复杂度估算(多项式、对数)
开发语言·c++·算法
MYX_3097 小时前
第三章 线型神经网络
深度学习·神经网络·学习·算法