1. 程式人生 > 實用技巧 >279. Perfect Squares(完全平方數)

279. Perfect Squares(完全平方數)

Description

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.
給定一個正整數 n,現需要用完全平方數(1, 4, 9

, 16)的和湊出 n,問所需平方數的最小值。

Examples

Example 1

Input: n = 12
Output: 3 
Explanation: 12 = 4 + 4 + 4.

Example 2

Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.

Solution

先給出一個 naive 的做法,暴力搜尋,大致基於下式進行計算(將題目所求的函式記為 \(f(n)\)):

\[f(n) = \begin{cases} 1& n 是完全平方數\\ \min(f(i) + f(n - i)) \; \{ i \in [1, n / 2] \} & else \end{cases} \]

為了防止重複計算,還是採用了記憶化搜尋的方法,結果華麗麗的 TLE 了(input = 6175),而我本地跑這組測試資料的時候直接爆棧了……

import kotlin.math.floor
import kotlin.math.min
import kotlin.math.sqrt

class Solution {
    private val memo = hashMapOf<Int, Int>()

    fun numSquares(n: Int): Int {
        if (memo.containsKey(n)) {
            return memo.getValue(n)
        }
        if (n.isPerfectSquare()) {
            memo[n] = 1
            return 1
        }
        var result = Int.MAX_VALUE
        for (i in 1..(n / 2)) {
            result = min(result, numSquares(i) + numSquares(n - i))
        }
        memo[n] = result
        return result
    }

    private fun Int.isPerfectSquare(): Boolean {
        val root = floor(sqrt(this.toDouble())).toInt()
        return root * root == this
    }
}

自然只能找其它解法了,以下一個解法來自 discussion。考慮以下等式(還是一樣,將原函式記為 \(f(n)\)):

\[f(0) = 0 \\ f(1) = f(0) + 1 = 1 \\ f(2) = f(1) + 1 = 2 \\ f(3) = f(2) + 1 = 3 \\ f(4) = \min(f(4 - 1 \times 1) + 1, f(4 - 2 \times 2) + 1) = \min(f(3) + 1, f(0) + 1) = 1 \\ f(5) = \min(f(5 - 1 \times 1) + 1, f(5 - 2 \times 2) + 1) = \min(f(4) + 1, f(1) + 1) = 2 \\ ... \\ f(13) = \min(f(13 - 1 \times 1) + 1, f(13 - 2 \times 2) + 1, f(13 - 3 \times 3) + 1) = \min(f(12) + 1, f(9) + 1, f(4) + 1) = 2 \]

所以最後的狀態轉移方程是:

\[f(n) = \min(f(n - i \times i) + 1)(其中 n - i \times i \geq 0 且 i \geq 1) \]

程式碼如下:

import kotlin.math.min

class Solution {
    fun numSquares(n: Int): Int {
        val dp = IntArray(n + 1) { Int.MAX_VALUE }
        dp[0] = 0
        for (i in 1..n) {
            var min = Int.MAX_VALUE
            var j = 1
            while (i - j * j >= 0) {
                min = min(min, dp[i - j * j] + 1)
                j++
            }
            dp[i] = min
        }
        return dp.last()
    }
}