Leetcode279完全平方数

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

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4
复制代码

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9
复制代码

我的心历路程: 碰到最少、最短这种字眼的话,现在我一开始想到的就是广度优先遍历 + 队列来解决这种类似问题。

思路就用图来解释吧

  1. 我们把给定的正整数12作为根节点,依次减去符合条件的完全平方数(小于n的完全平方数)
  2. 减去后得到了 11 8 3 这三个数字,三个数字继续减去 1 4 9 ,又得到了他们的差
  3. 继续减 1 4 9,直到碰到第一个0为止,说明找到了最短的路径。对应的层数就是最少个数

“Talk is cheap. Show me the code.” --Linus Torvalds

/**
 * 广度优先搜索,耗时严重,思路:
 *  1. 计算出可能的完全平方数的数组
 *  2. 设置一个数组,初始化元素为传入的正整数
 *  3. 设置一个step为0
 *  3. 循环这个数组,让数组中的每个元素都删除完全平方数中的每一个元素,第一个差为0的,说明就是最短路径
*/
var numSquares = function(n) {
  let MaxSquare = Math.ceil(Math.sqrt(n));

  function getAvailableSquareArr(num) {
    let arr = [];
    for (let i = 1; i<=num; i++) {
      arr.push(Math.pow(i, 2));
    }
    return arr;
  }
  let squareArr = getAvailableSquareArr(MaxSquare); // 可能的完全平方数组成的数组
  let squareLength = squareArr.length; // 完全平方数组长度
  let subArr = [n]; // 用来存放每次减去完全平方数的数组,初始值为传入的正整数
  let step = 0;
  while(subArr.length) {
    step++;
    let len = subArr.length;
    for (let j = 0; j < len; j++) {
      let value = subArr.shift(); // 队首出队列
      for (let i = 0; i < squareLength; i++) {
        let sub = value - squareArr[i];
        if (sub === 0) return step;
        if (sub < 0) {
          break;
        }
        if (!subArr.includes(sub)) {
          subArr.push(sub);
        }
      }
    }
  }
  return step;
};
复制代码

上面的这种解法我们看一下执行耗时

竟然要解决6s的时间,可见这种方式固然能够求出结果,但是其计算的效率并不高。我们来分析一下他的时间复杂度是多少:

  1. 最外层一个while循环
  2. 内部一个嵌套的for循环

得到时间复杂度为O(n^3),这里时间复杂度我个人也不大确定是否为O(n^3),如果您知道怎么算,请在评论解惑。

所以我们需要一种更高效的方式来计算。于是引入了动态规划的方式解决该题。动态规划传送门

来自wiki百科:动态规划常常适用于有重叠子问题[1]和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。

动态规划解决这个问题我也在leecode上看过解释,但是自己就是看不大懂。直到写文章的今天我也还是一知半解。所以使用动态规范解决的方式我现在的水平还讲不清楚,但是我先把动态规划解决的代码放在这,待日后补充具体的思路。各位大佬如果觉得能够讲清楚,也可以在留言处写下自己的思路。带带小弟我。

function numSquares(n) {
  const dp = [0];
  
  for (let i = 1; i <= n; i++) {
    dp[i] = Number.MAX_VALUE;
    for (let j = 1; j*j <= i; j++) {
      dp[i] = Math.min(dp[i], dp[i-j*j]+1);
    } 
  }
  
  return dp[n];
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值