https://www.lintcode.com/problem/2-keys-keyboard/description
有一个笔记本,上面写了一个字符 A A A,每次可以执行下面两个操作之一: 1、复制笔记本上已经存在的所有字符; 2、粘贴上一次复制过的字符。 问要让笔记本上恰好出现 n n n个 A A A至少需要多少次操作。
法1:动态规划。设 f [ n ] f[n] f[n]为恰好出现 n n n个 A A A至少需要多少次操作。如果 n n n是素数,那么执行第二个步骤的时候,”上一次“复制过的字符数量一定是 1 1 1,否则的话,就会使得 n n n有非 1 1 1的约数,矛盾。所以如果 n n n是素数,那么需要的操作次数就是 n n n;考虑 n n n的最大真因子 k k k,我们接下来证明: f [ n ] = f [ k ] + n / k f[n]=f[k]+n/k f[n]=f[k]+n/k也就是说,如果素因子分解 n = p 1 s 1 p 2 s 2 . . . p t s t n=p_1^{s_1}p_2^{s_2}...p_t^{s_t} n=p1s1p2s2...ptst,那么 f [ n ] = p 1 s 1 + p 2 s 2 + . . . + p t s t f[n]=p_1s_1+p_2s_2+...+p_ts_t f[n]=p1s1+p2s2+...+ptst数学归纳法。当 n = 1 , 2 , 3 n=1,2,3 n=1,2,3的时候显然正确。假设结论对于 n − 1 n-1 n−1是成立的,对于 n n n,假设最后一次执行copy的时候是笔记本上有 i i i个字符的时候,那么总次数应该是 f [ i ] + n / i f[i]+n/i f[i]+n/i,设 i = p 1 r 1 p 2 r 2 . . . p t r t i=p_1^{r_1}p_2^{r_2}...p_t^{r_t} i=p1r1p2r2...ptrt,其中 r j ≤ s j r_j\le s_j rj≤sj,所以 f [ i ] + n / i = p 1 r 1 + p 2 r 2 + . . . + p t r t + p 1 s 1 − r 1 p 2 s 2 − r 2 . . . p t s t − r t f[i]+n/i=p_1r_1+p_2r_2+...+p_tr_t+p_1^{s_1-r_1}p_2^{s_2-r_2}...p_t^{s_t-r_t} f[i]+n/i=p1r1+p2r2+...+ptrt+p1s1−r1p2s2−r2...ptst−rt也就是要证明 p 1 ( s 1 − r 1 ) + p 2 ( s 2 − r 2 ) + . . . + p t ( s t − r t ) ≤ p 1 s 1 − r 1 p 2 s 2 − r 2 . . . p t s t − r t p_1(s_1-r_1)+p_2(s_2-r_2)+...+p_t(s_t-r_t)\le p_1^{s_1-r_1}p_2^{s_2-r_2}...p_t^{s_t-r_t} p1(s1−r1)+p2(s2−r2)+...+pt(st−rt)≤p1s1−r1p2s2−r2...ptst−rt令 s m − r m = x m ≥ 0 s_m-r_m=x_m\ge 0 sm−rm=xm≥0,即要证明: p 1 x 1 + . . . + p t x t ≤ p 1 x 1 . . . p t x t p_1x_1+...+p_tx_t\le p_1^{x_1}...p_t^{x_t} p1x1+...+ptxt≤p1x1...ptxt这可以由数学归纳法证明,当 t = 1 t=1 t=1的时候, p 1 x 1 ≤ p 1 x 1 p_1x_1\le p_1^{x_1} p1x1≤p1x1相当于要证 g ( x 1 ) = p 1 x 1 − 1 − x 1 ≥ 0 g(x_1)=p_1^{x_1-1}-x_1\ge 0 g(x1)=p1x1−1−x1≥0, x 1 = 0 , 1 x_1=0,1 x1=0,1的时候都是对的,求导得 g ′ ( x 1 ) = p 1 x 1 − 1 ln p 1 − 1 ≥ 0 , x 1 ≥ 2 g'(x_1)=p_1^{x_1-1}\ln p_1-1\ge 0,x_1\ge 2 g′(x1)=p1x1−1lnp1−1≥0,x1≥2所以 g ( x 1 ) ≥ 0 g(x_1)\ge 0 g(x1)≥0恒成立,所以 t = 1 t=1 t=1的时候成立;假设对于 t − 1 t-1 t−1成立,则由归纳假设知 p 1 x 1 + . . . + p t x t ≤ p 1 x 1 . . . p t − 1 x t − 1 + p t x t p_1x_1+...+p_tx_t\le p_1^{x_1}...p_{t-1}^{x_{t-1}}+p_tx_t p1x1+...+ptxt≤p1x1...pt−1xt−1+ptxt即要证明 h ( x t ) = p 1 x 1 . . . p t − 1 x t − 1 ( p t x t − 1 ) − p t x t ≥ 0 h(x_t)=p_1^{x_1}...p_{t-1}^{x_{t-1}}(p_t^{x_t}-1)-p_tx_t\ge 0 h(xt)=p1x1...pt−1xt−1(ptxt−1)−ptxt≥0,当 x t = 0 , 1 x_t=0,1 xt=0,1的时候都成立,求导得 h ′ ( x t ) = p 1 x 1 . . . p t − 1 x t − 1 p t x t ln p t − p t ≥ 0 , x t ≥ 2 h'(x_t)=p_1^{x_1}...p_{t-1}^{x_{t-1}}p_t^{x_t}\ln p_t-p_t\ge 0, x_t\ge 2 h′(xt)=p1x1...pt−1xt−1ptxtlnpt−pt≥0,xt≥2所以 h ( x t ) ≥ 0 h(x_t)\ge 0 h(xt)≥0,由数学归纳法, p 1 x 1 + . . . + p t x t ≤ p 1 x 1 . . . p t x t p_1x_1+...+p_tx_t\le p_1^{x_1}...p_t^{x_t} p1x1+...+ptxt≤p1x1...ptxt成立,所以对于任意的 i ∣ n i|n i∣n,都有 f [ i ] + n / i ≥ f [ k ] + n / k f[i]+n/i\ge f[k]+n/k f[i]+n/i≥f[k]+n/k,所以当 i i i取 n n n的最大真因子的时候操作次数最少。所以算法成立。代码如下:
public class Solution { /** * @param n: The number of 'A' * @return: the minimum number of steps to get n 'A' */ public int minSteps(int n) { // Write your code here int[] dp = new int[n + 1]; for (int i = 2; i <= n; i++) { dp[i] = i; for (int j = i / 2; j >= 1; j--) { // 找到最大真因子了 if (i % j == 0) { dp[i] = dp[j] + i / j; break; } } } return dp[n]; } }时空复杂度 O ( n ) O(n) O(n)。
注解: 这里的动态规划的意味看起来并不浓厚。实际上
if (i % j == 0) { dp[i] = dp[j] + i / j; break; }这一句,如果改为:
if (i % j == 0) { dp[i] = Math.min(dp[i], dp[j] + i / j); }那么按照最后一步分类的意味会更加明显(这里是按照多少个字符的时候最后一次执行copy来分类的),更有动态规划的味道。只不过可以用数学证明,数到最大真因子的时候就能得到最优解而break出来了而已。
法2:根据上面的公式 f [ n ] = p 1 s 1 + p 2 s 2 + . . . + p t s t f[n]=p_1s_1+p_2s_2+...+p_ts_t f[n]=p1s1+p2s2+...+ptst可以直接计算。代码如下:
public class Solution { /** * @param n: The number of 'A' * @return: the minimum number of steps to get n 'A' */ public int minSteps(int n) { // Write your code here int res = 0; for (int i = 2; i <= n; i++) { // 累加s个p while (n % i == 0) { res += i; n /= i; } } return res; } }时间复杂度 ∑ s i \sum{s_i} ∑si,空间 O ( 1 ) O(1) O(1)。