leetcode 650. 只有兩個鍵的鍵盤
阿新 • • 發佈:2018-12-08
https://leetcode-cn.com/problems/2-keys-keyboard/description/
題目描述
最初在一個記事本上只有一個字元 ‘A’。你每次可以對這個記事本進行兩種操作:
Copy All (複製全部)
: 你可以複製這個記事本中的所有字元(部分的複製是不允許的)。
Paste (貼上)
: 你可以貼上你上一次複製的字元。
給定一個數字 n
。你需要使用最少的操作次數,在記事本中打印出恰好 n
個 ‘A’。輸出能夠打印出 n
個 ‘A’ 的最少操作次數。
樣例
輸入: 3 輸出: 3 解釋: 最初, 我們只有一個字元 'A'。 第 1 步, 我們使用 Copy All 操作。 第 2 步, 我們使用 Paste 操作來獲得 'AA'。 第 3 步, 我們使用 Paste 操作來獲得 'AAA'。
注意
n
的取值範圍是 [1, 1000]。
演算法1
(動態規劃) $O(n)$
- 設計狀態 $f(i)$,表示構成 $i$ 個 A 所需要的最少步數,注意這裡只需要計算是 n 的約數的狀態,即狀態 $i$,滿足 $n \% i == 0$。
- 初始時,$f(1) = 0$,其餘為正無窮;每次轉移時,列舉 $i$ 非自身的約數 $j$,即 $i \% j==0$ 並且 $j \neq i$,則有 $f(i) = \min (f(i), f(j) + \frac{i}{j})$。
- 最終答案為 $f(n)$。
時間複雜度
- 狀態數有 $O(\sqrt n)$個,每個狀態的轉移有 $O(\sqrt n)$個,故總時間複雜度為 $O(n)$。
C++ 程式碼
class Solution { public: int minSteps(int n) { vector<int> f(n + 1, INT_MAX); f[1] = 0; for (int i = 2; i <= n; i++) if (n % i == 0) for (int j = 1; j * j <= n; j++) if (i % j == 0) { f[i] = min(f[i], f[j] + i / j); if (i > 1) f[i] = min(f[i], f[i / j] + j); } return f[n]; } };
演算法2
(數學) $O(\sqrt n)$
- 假設已經得到了 n 的所有質因數 $a_1, a_2, …, a_i$,則n 的分解方式可能如下:最後一步必定是由 $a_1$ 組 $n/a_1$ 個 A 拷貝貼上而成;同理,倒數第二步一定是由 $a_2$ 組 $n / a_1 / a_2$ 個 A 拷貝貼上而成;直到第一步為止。可以發現不管分解的順序如何,最終答案都是質因數之和。核心思想即貼上的次數為質數次,否則可以有更有的分解方式。
- 簡要證明如下,設當前組成某個長度的字串需要 $p$ 步,若直接拷貝達到目標需要 $q$ 次貼上,總共需要 $p+q$步完成;若 $q$ 為合數,即 $q = ij$ 且 $i>1, j>1$,則可以將拷貝分解為兩次,先複製拷貝 $i$ 次,然後再複製拷貝 $j$ 次,這樣需要 $p+i+j$ 步完成,顯然 $ij >= i+j$。所以只需要所有質因數累加即可。
時間複雜度
- 質因數的個數為 $O(\sqrt n)$,故時間複雜度為 $O(\sqrt n)$。
C++ 程式碼
class Solution { public: int minSteps(int n) { int ans = 0; int i = 2; while (n > 1) { while (n % i == 0) { n /= i; ans += i; } i++; } return ans; } };