题目介绍
最初记事本上只有一个字符 ‘A’ 。你每次可以对这个记事本进行两种操作:
Copy All(复制全部):复制这个记事本中的所有字符(不允许仅复制部分字符)。
Paste(粘贴):粘贴 上一次 复制的字符。
给你一个数字 n ,你需要使用最少的操作次数,在记事本上输出 恰好 n 个 ‘A’ 。返回能够打印出 n 个 ‘A’ 的最少操作次数。
题目样例
示例 1:
输入:3
输出:3
解释:
最初, 只有一个字符 'A'。
第 1 步, 使用 Copy All 操作。
第 2 步, 使用 Paste 操作来获得 'AA'。
第 3 步, 使用 Paste 操作来获得 'AAA'。
示例 2:
输入:n = 1
输出:0
提示:
1 <= n <= 1000
题目解析
通过读题我们知道一个长度为n的字符串必然是由上一次操作之前的字符串和剪切板拼接的字符串组成的。
这里我们进行未知量的假设
假设
$S_k$为第k次操作得到的字符串长度
$Paste_k$为第k次操作复制的字符长度
因此,对于一个长度为n的字符串假设最小操作次数为k
得,
$n$ = $Paste_k+S_{i-1}$
由题意知,
$\exists!j\in[0,i-1]$,有$S_j=Paste_{i-1}$
则有
$Paste_k =\begin{cases}
S_{i-1} & ,j == i-1\\\\
Paste_{j+1} & ,else
\end{cases}$
$N$
$=Paste_k+S_{i-1}$
$= \sum_{i = j+1}^{k} Paste_{j+1}+S_j$
$=(k-j)*S_j$
$=(n/S_j)*S_j$
这里我们发现$S_j$实际是n的因子
最终我们就得出了次数的关系式
$Time_n = (n/S_j)+S_j$
显然最大因子的次数是比其他因子少的
因为最大因子与数字的倍数是最小的
比如$time_{100}$的两个因子10和20
根据公式
$time_{100} = 100/20+
time_{20} = 5+2+time_{10}$
$time_{100} = 100/10+time_{10} = 10+time_{10}$
可以看出差距极大
算法实现
1.DFS版
class Solution {
private:
public:
int minSteps(int n) {
if(n == 1)
return 0;
int &&mid = n>>1;
int i = mid;
while(i>1&&n%i)
i--;
return n/i+minSteps(i);
}
Solution(){
}
};
2.DFS版打表
class Solution {
private:
int num[257];//这里只对于重复率高的进行打表,实际257~512那层只进行了一次运算重复率降低因此这里定义的是512
public:
int minSteps(int n) {
if(n<257&&num[n]+1)
return num[n];
int &&mid = n>>1;
int i = mid;
while(i>1&&n%i)
i--;
int last;
if(i<257)
num[i] = num[i]==-1?minSteps(i):num[i];
last = i<257?num[i]:minSteps(i);
if(n<257){
num[n] = num[n]==-1?n/i+num[i]:num[n];
return num[n];
}
return n/i+last;
}
Solution(){
memset(num,-1,257*sizeof(int));
num[0] = num[1] = 0;
num[2] = 2;
}
};
3.BFS版打表
bfs打表这里空间换时间也使用了O(n)的打表预处理时间,实际上降低了平均损耗,但时间上实际是比dfs多了20ms的损耗,也证明了命中率相对较低
class Solution {
private:
int num[1010];
public:
int minSteps(int n) {
return num[n];
}
Solution(){
memset(num,0,1010*sizeof(int));
num[0] = num[1] = 0;
num[2] = 2;
for(int i = 3;i<1010;i++){
int &&mid = i>>1;
int j = mid;
while(j>1&&i%j)
j--;
num[i] = num[j]+i/j;
}
}
};