1. 程式人生 > >leetcode 650. 只有兩個鍵的鍵盤

leetcode 650. 只有兩個鍵的鍵盤

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)$
  1. 設計狀態 $f(i)$,表示構成 $i$ 個 A 所需要的最少步數,注意這裡只需要計算是 n 的約數的狀態,即狀態 $i$,滿足 $n \% i == 0$。
  2. 初始時,$f(1) = 0$,其餘為正無窮;每次轉移時,列舉 $i$ 非自身的約數 $j$,即 $i \% j==0$ 並且 $j \neq i$,則有 $f(i) = \min (f(i), f(j) + \frac{i}{j})$。
  3. 最終答案為 $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)$
  1. 假設已經得到了 n 的所有質因數 $a_1, a_2, …, a_i$,則n 的分解方式可能如下:最後一步必定是由 $a_1$ 組 $n/a_1$ 個 A 拷貝貼上而成;同理,倒數第二步一定是由 $a_2$ 組 $n / a_1 / a_2$ 個 A 拷貝貼上而成;直到第一步為止。可以發現不管分解的順序如何,最終答案都是質因數之和。核心思想即貼上的次數為質數次,否則可以有更有的分解方式。
  2. 簡要證明如下,設當前組成某個長度的字串需要 $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;
    }
};