题目链接

题目描述:

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

实例:

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.

解题思路

  可以将整个问题转化为图论问题。现在对该问题建模:

  • 从n到0每个数字代表每个结点;
  • 如果两个数字相差一个完全平方数,则在两结点之间建立一条边;
      这样就得到了一个无权图,原问题转化为求这个无权图从n到0的最短路径。
    279完全平方数(优化思路) - 图1
      既然转化为最短路径问题,那就不得不提到队列了,进而该问题转化为队列实现BFS(广度优先搜索)。

代码

  1. class Solution {
  2. public:
  3. int numSquares(int n) {
  4. queue< pair<int,int> > q; //1.当前数字;2.到当前数字的路径的段数
  5. q.push(make_pair(n,0));
  6. while(!q.empty()){
  7. int num = q.front().first;
  8. int step = q.front().second;
  9. q.pop();
  10. if(num == 0){
  11. return step;
  12. }
  13. for(int i = 1; num - i*i >= 0; i++){
  14. q.push(make_pair(num - i*i, step + 1));
  15. }
  16. }
  17. throw invalid_argument("No Solution");
  18. }
  19. };

  遗憾的是,在leetcode并不能通过,why?
279完全平方数(优化思路) - 图2
  这看起来像一个BFS,但实际上不是(可以好好看看教材上BFS遍历图的过程,当遇到访问过的结点会怎么做?)。原因在于,循环入队过程中,会有重复结点入队。比如,对于结点1来说,5-4=12-1=110-9=1等等。我们当前用图建模而不是树建模,对于树结构来说,每一个结点只有一个父结点,意味着到达该结点途径唯一;而对于图结构来说,到达某一结点的路径不唯一。所以,当n足够大时,冗余结点过多,会超出时间限制。

优化1

  既然当前有重复结点存在,那么就让它变得“唯一”。如何做?设置一个长度为n+1的数组,表示从0到n的某一结点是否已经被访问过,访问过置为true,未访问过置为false。循环入队的过程中,当该结点未被访问过,则可以入队。

代码

  1. class Solution {
  2. public:
  3. int numSquares(int n) {
  4. queue< pair<int,int> > q; //1.当前数字;2.到当前数字的路径的段数
  5. q.push(make_pair(n,0));
  6. vector<bool> visited(n + 1, false); //表示从0到n,是否已经被访问过
  7. visited[n] = true;
  8. while(!q.empty()){
  9. int num = q.front().first;
  10. int step = q.front().second;
  11. q.pop();
  12. if(num == 0){
  13. return step;
  14. }
  15. for(int i = 1; num - i*i >= 0; i++){
  16. if(!visited[num - i*i]){
  17. q.push(make_pair(num - i*i, step + 1));
  18. visited[num - i*i] = true;
  19. }
  20. }
  21. }
  22. throw invalid_argument("No Solution");
  23. }
  24. };

279完全平方数(优化思路) - 图3
  虽然leetcode通过了我们的代码,但是执行用时不太友好,显然我们可以再进一步优化。

优化2

  我们通过观察发现了两处优化。
  首先,我们发现在循环入队部分,每一轮num - i*i计算执行了四次,显然这一部分可以做文章。
  其次,另一处优化点,在于是否可以在循环中提前返回最短路径。因为当num - i*i == 0时,显然下一结点为终点(零结点),我们可以返回step + 1,提前一轮结束遍历。

代码

  1. class Solution {
  2. public:
  3. int numSquares(int n) {
  4. queue< pair<int,int> > q; //1.当前数字;2.到当前数字的路径的段数
  5. q.push(make_pair(n,0));
  6. vector<bool> visited(n + 1, false); //表示从0到n,是否已经被访问过
  7. visited[n] = true;
  8. while(!q.empty()){
  9. int num = q.front().first;
  10. int step = q.front().second;
  11. q.pop();
  12. for(int i = 1; ; i++){
  13. int n = num - i*i;
  14. if(n < 0)
  15. break;
  16. if(n == 0)
  17. return step + 1;
  18. if(!visited[n]){
  19. q.push(make_pair(n, step + 1));
  20. visited[n] = true;
  21. }
  22. }
  23. }
  24. throw invalid_argument("No Solution");
  25. }
  26. };

279完全平方数(优化思路) - 图4

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。