给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4
复制代码
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9
复制代码
我的心历路程: 碰到最少、最短这种字眼的话,现在我一开始想到的就是广度优先遍历 + 队列来解决这种类似问题。
思路就用图来解释吧
- 我们把给定的正整数12作为根节点,依次减去符合条件的完全平方数(小于n的完全平方数)
- 减去后得到了 11 8 3 这三个数字,三个数字继续减去 1 4 9 ,又得到了他们的差
- 继续减 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的时间,可见这种方式固然能够求出结果,但是其计算的效率并不高。我们来分析一下他的时间复杂度是多少:- 最外层一个while循环
- 内部一个嵌套的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];
}
复制代码