1. 程式人生 > 其它 >LeetCode-279 完全平方數

LeetCode-279 完全平方數

題目來源

LeetCode-279.完全平方數

題目詳情

給定正整數n,找到若干個完全平方數(比如1, 4, 9, 16, ...)使得它們的和等於 n。你需要讓組成和的完全平方數的個數最少。

給你一個整數 n ,返回和為 n 的完全平方數的 最少數量

完全平方數 是一個整數,其值等於另一個整數的平方;換句話說,其值等於一個整數自乘的積。例如,14916 都是完全平方數,而 311 不是。

示例1:

輸入: n = 12
輸出: 3
解釋: 12 = 4 + 4 + 4

示例 2:

輸入: n = 13
輸出: 2
解釋: 13 = 4 + 9

提示:

  • 1 <= n <= 104

題解分析

完全揹包問題變形-二維陣列解法

  1. 從題目可以分析出,這題其實是完全揹包問題的變形,整個題目都能看到完全揹包問題的影子。
  2. 題目中的n就可以類比於揹包的容量,這些計算出來的完全平方數就相當於商品,可以假設每個完全平方數的價值為1(相當於把個數看成價值)。
  3. 由此,可以構造出狀態轉移陣列,假設\(dp[i][j]\)表示前i個數中,可以拼湊出數字和恰好是j的最小數字個數。進而,根據定義,可以定義狀態轉移函式:\(dp[i][j] = Math.min(dp[i-1][j], dp[i][j-nums[i]] + 1)\)
  4. 需要注意的是,這裡的狀態轉移方程中的第二個條件,dp[i][j-1]是與01揹包不同的地方,因為這些平方數可以選擇無限個,其實\(dp[i][j-nums[i]] + 1\)
    是應該寫成\(dp[i-1][j-k*nums[i]] + k\)(k表示平方數使用的個數)的形式的。但是我們仔細觀察迭代式可以知道:其實\(dp[i][j-nums[i]] + 1\)已經計算過了這個式子:\(dp[i-1][j-k*nums[i]] + k\)。所以在狀態轉移方程裡,我們可以直接寫成複用前一個式子的形式。
  5. 最後,我們還得考慮一下邊界值。\(dp[i][0]\)可以表示為前i個數中選出若干個數的和為0的最小個數,這其實可以賦值為0,因為不可能拼湊出0,因為最小的平方數都是1。而\(dp[0][j]\)則可以理解為用0個數拼湊出j是絕不可能的,所以賦值為最大值0X3F3F3F3F。
class Solution {
    final int MAXS = 0X3F3F3F3F;
    public int numSquares(int n) {
        int sn = (int)Math.sqrt(n);
        int[] nums = new int[sn+1];
        for(int i=0; i<=sn; i++){
            nums[i] =  i * i;
        }
        // dp[i][j] = min(dp[i-1][j], dp[i][j-nums[i]])
        int[][] dp = new int[sn+1][n+1];
        for(int i =0; i<=sn; i++){
            Arrays.fill(dp[i], MAXS);
            dp[i][0] = 0;
        }
        
        for(int i=1; i<=sn; i++){
            for(int j=1; j<=n; j++){
                if(j >= nums[i]){
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-nums[i]] + 1);
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[sn][n];
    }
}

完全揹包優化-一維陣列解法

  1. 學過揹包問題的可能都有所瞭解,揹包問題一般都可以進行陣列維度的優化。
  2. 從我們的狀態轉移方程(\(dp[i][j] = Math.min(dp[i-1][j], dp[i][j-nums[i]] + 1)\))也可以看到,當前狀態只依賴於上一層的狀態以及本輪之前已經計算過的狀態。所以,可以將dp退化為一維陣列,然後採用正序遍歷j的方法,這麼做的原因是需要取到本輪迭代中前面計算的值\(dp[i][j-nums[i]]\)
  3. 最後,也要考慮一下邊界值的計算,這裡只有\(dp[0]\)可以被賦值為0,其他位置(類似於解法一中的\(dp[0][j]\))都被賦值為無窮大:0X3F3F3F3F。
  4. 程式碼如下:
class Solution {
    final int MAXS = 0X3F3F3F3F;
    public int numSquares(int n) {
        int sn = (int)Math.sqrt(n);
        int[] nums = new int[sn+1];
        for(int i=0; i<=sn; i++){
            nums[i] =  i * i;
        }
        // dp[i][j] = min(dp[i-1][j], dp[i][j-nums[i]])
        int[] dp = new int[n+1];
        Arrays.fill(dp, MAXS);
        dp[0] = 0;
        
        for(int i=1; i<=sn; i++){
            for(int j=nums[i]; j<=n; j++){
                dp[j] = Math.min(dp[j], dp[j-nums[i]] + 1);
            }
        }
        return dp[n];
    }
}
Either Excellent or Rusty