这道题目看似平平无奇,AC的过程中遇到了不少坑。
首先我们按照最直观、最快速的解法来写代码:

class Solution {
public:
    int numSquares(int n) {
        int sum = 0;
        int num = n;
        int cnt = 0;
        while(num > 0) {
            while(n >= sum + num && is_squares(num))
            {
                sum += num;
                cnt += 1;
                if(sum == n) return cnt;
            }
            num--;
        }
        return cnt;
    }
private:
    bool is_squares(int n){
        int num = sqrt(n);
        if(num * num == n)
            return true;
        return false;
    }
};

发现WA:

Submission Detail
312 / 588 test cases passed.
Status: Wrong Answer
Submitted: 4 minutes ago
Input:
12
Output:
4
Expected:
3

说明我们的贪心策略是无法解决这类问题的,例如上面的12,正确的解法应该是4+4+4.
而我们给出得答案是9+1+1+1.
上面代码另一个需要注意的地方是函数is_squares,最开始我写成了:

bool is_squares(int n){
    if(sqrt(n) * sqrt(n) == n)
        return true;
    return false;
    }

这里输入11返回了true,而正确的解法应该是将sqrt返回的结果赋值给int。
那么如何解决12这个场景下最少数据的个数呢?这个时候很自然的想到了动态规划。
首先定义dp[i]表示对数字i来说最少需要多少个完全平方数来构成。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0] = 0;
        for(int i = 1;i<=n;i++)
        {
            for(int j = 1;j*j <= i;j++)
                dp[i] = min(dp[i],dp[i-j*j] + 1);
        }
        return dp[n];
    }
};

动态规划的解法比较简单,如果想不明白可以借助下图:

例如dp[25]=dp[25-4*4] + 1,因为4是完全平方数,所以我们加1,这样将dp[25]转换为求解dp[9],而dp[9]=dp[9-3*3] + 1= dp[0] + 1 = 1,我们是按照逆序求解,可以通过递归的方式实现,而我们通过爬楼梯问题也知道正向求解(从小到大)效率更高,避免了重复子问题的计算。
因此,从形式上来看一定是两层循环,第一层循环是我们计算从dp[1]到dp[n]的值,内层循环是计算dp[i]的具体值。这里假设i-k(k是完全平方数,并且k小于i)。
运行效率:

Runtime: 184 ms, faster than 53.88% of C++ online submissions for Perfect Squares.
Memory Usage: 9 MB, less than 66.35% of C++ online submissions for Perfect Squares.

BFS方式实现

class Solution {
public:
    int numSquares(int n) {
        //去重 保存已求得的和
        unordered_set<int> visited;
        queue<int> q;
        //保存小于等于n的完全平方数
        vector<int> perfect;
        int step = 1;
        for(int i = 1;i*i<=n;i++)
        {
            int num = i*i;
            q.push(num);
            perfect.push_back(num);
            visited.insert(num);
        }
        //如果最后一个元素等于n,则只需要一个
        if(q.back() == n) return 1;
        
        while(!q.empty())
        {
            step += 1;
            int size = q.size();
            for(int i = 0;i < size;i++)
            {
                int num = q.front();
                for(auto elem : perfect)
                {
                    //遍历完全平方数vector,每次相加判断和n的关系
                    int sum = num + elem;
                    if(sum > n) break;
                    if(sum == n) return step;
                    if(sum < n && !visited.count(sum))
                    {
                        visited.insert(sum);
                        q.push(sum);
                    }
                }
                q.pop();
            }
        }
        
        return -1;
    }
};

运行效率:

Runtime: 112 ms, faster than 74.35% of C++ online submissions for Perfect Squares.
Memory Usage: 26.2 MB, less than 11.14% of C++ online submissions for Perfect Squares.