题目描述
难度中等380收藏分享切换为英文关注反馈
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n =12
输出: 3 解释:12 = 4 + 4 + 4.
示例 2:
输入: n =13
输出: 2 解释:13 = 4 + 9.
老实说看到这道题我第一想法应该是用动态规划做,但是只知道动态规划是利用前面的结果来进行后一步计算,实际没有写过,而这道题正好是在BFS卡片里面的,所以就用BFS来写了,总的来说跟上一道题打开转盘锁是一个套路,首先找出目标数的完全平方数组,然后数组里的每个数都是一个分支,用队列实现,代码如下:
class Solution {
public:
vector<int>getSquares(int n) { //找到符合要求完全平方数组
vector<int> res;
for (int i = 1; i * i <= n; i++)
{
res.push_back(i * i);
}
return res;
}
int numSquares(int n) {
vector<int>squarearr = getSquares(n);
queue<int>q; //队列用来实现BFS
q.push(n);
int ans = 1; //这里就算目标在完全平方数组里的返回值也应该是1
unordered_set<int> squareset(squarearr.begin(), squarearr.end()); //unordered_set 用过真的觉得好用
unordered_set<int> visited; //建立访问过的unordered_set,避免重复劳动
visited.insert(n);
if (squareset.count(n) != 0) return 1;
while (!q.empty())
{
int size = q.size(); //size为同一层或者说同一步骤下节点的个数
ans++; //每多一层答案+1
for (int i = 0; i < size; i++)
{
int temp2; //temp2用来构建临时结果
int temp = q.front(); //temp用来储存当前出队讨论的项,用temp就避免了temp2在下面会因为-num而改变值的情况
q.pop();
for (int num : squarearr) //C++11新特性,基于范围的遍历
{
temp2 = temp - num; //父节点的值减去完全平方数得到子节点的值,检查是否满足要求
if (squareset.count(temp2) != 0) return ans; //满足要求,返回结果
if (visited.count(temp2) != 0) continue; //访问过,continue
if (temp2 < 0) break; //小于0说明超过上界了无需继续讨论
q.push(temp2);
visited.insert(temp2);
}
}
}
return n; //最坏情况由N个1组成
}
};
时间复杂度:粗略估算O(n^1.5),一个n是需要遍历从1到N的所有数(实际上应该不用),n^0.5是每一个数的遍历都要遍历一次square数组
空间复杂度:O(n),主要是visited这个unordered_set的空间,另外还有squarearr,queue,都小于n,因此合并为O(n)
写了几道BFS的题目之后总结一下这类题型的关键点:
1. 弄清楚起始条件和最终条件,中间是通过什么样的边来链接,比如这题的边是完全平方数组,转盘锁那道题是锁的位数*转的方向,知道这个了就找到了BFS树中的边;
2. 明确起点即终点情况的返回值,此题是1,转盘锁是0;
3. 什么时候结果+1,临时值temp的改变发生在哪一个循环里(!queue.empty()还是range(0,size)),这一步很大程度上决定了你的结果是否有问题,出错最多的地方也在这里
4. 考虑各种能跳出循环的条件,减少时间消耗, 比如此题中的continue, break
本题还有其他多种方法,下次尝试时将试着用动态规划写一下,还有数学结论法,简直就是降维打击;慢慢刷题吧