![[JavaScript 刷题] DP - 组成整数的最小平方数数量, leetcode 279,第1张 [JavaScript 刷题] DP - 组成整数的最小平方数数量, leetcode 279,第1张](/aiimages/%5BJavaScript+%E5%88%B7%E9%A2%98%5D+DP+-+%E7%BB%84%E6%88%90%E6%95%B4%E6%95%B0%E7%9A%84%E6%9C%80%E5%B0%8F%E5%B9%B3%E6%96%B9%E6%95%B0%E6%95%B0%E9%87%8F%2C+leetcode+279.png)
上一篇 组成整数的最小平方数数量, leetcode 279 中使用了 BFS 的方式实现,这里使用 DP 完成。
其实这道题和 DP 地经典切段、选择硬币的套路还是一样的。
279. Perfect Squares
题目如下:
解题思路Given an integer
n, return the least number of perfect square numbers that sum ton.A perfect square is an integer that is the square of an integer; in other words, it is the product of some integer with itself. For example,
1,4,9, and 16 are perfect squares while3and11are not.
和常用的 DP 解决方案一样,这里也会需要一个容器去保存之前计算过的数值,毕竟递归的时间复杂度是
2
n
2^n
2n,将之前计算过的数值保存在容器中这一过程又称之为 memoization,也是 React 里面的 useMemo 的技巧。
这里有一个遍历是逃不掉的,就是完美平方数的遍历,以之前画的图为例:
每一层都需要对 1, 4, 9 进行迭代。
使用 memoization 的技巧就在于,当进行 degree = 3 的迭代是,degree = 2 的值会被保存在容器中,因此无需使用递归再一次进行计算。这也就是斐波那契数列在使用递归方法的复杂度为
O
(
2
n
)
O(2^n)
O(2n),但是使用 DP 的复杂度却只有
O
(
n
)
O(n)
O(n) 的原因。
具体实现方式如下:
新建一个长度为 n+1 的数组
这里直接初始化成 n+1 是因为数组是 0-based indexed,而且后面会需要用到
0
+
k
2
0 + k^2
0+k2 获取完美平方数本身。
后面做数值相加的时候,如 13 的组合包含 9 + 4 9 + 4 9+4,也就是 3 2 + 2 2 3^2 + 2^2 32+22,这样可以省去一步重复计算的步骤。
这里初始化的值可以用 n,也可以取无穷大,差别不是很大。
因为已知返回最大值是 n 自身——也就是 n 个
1
2
1^2
12 相加,这些可以从一些边界值可以得出,如 1, 2, 3 等。
从 index 1 开始计算
这里获取 1 的唯一方式是 0 + 1 2 0 + 1^2 0+12,在对比 n n n 与 0 + 1 2 0 + 1^2 0+12 后取较小值,也就是 1.
index = 2
这里开始会进入使用 memoization 的情况。
2 的获取方式其实是这么来的:
a r r [ 1 ] + 1 2 arr[1] + 1^2 arr[1]+12
分别来源于
1
2
1^2
12 ——从完美平方数的循环中获取,加上获取 1 这个值所需的最小值。
index = 3
3 的获取方式同理:
a r r [ 2 ] + 1 2 arr[2] + 1^2 arr[2]+12
i
<
n
i
index = 4
从 4 之后开始就已经包含一些特殊值了,因为 4 的获取方式可以通过 2 2 2^2 22,这是一个完美平方数的值。
从 4 以后的数值会遇到两种情况:
通过完美平方数本身可以获取通过组合完美平方数进行获取在这种情况下就要进行判断,并且取最小值,这里的对比就是:
n n n 与 0 + 2 2 0 + 2^2 0+22 的对比,取最小值自然是 1 了。
index = 5
这里也是同样的方式进行 *** 作,最后判断 a r r [ 2 ] + 1 2 arr[2] + 1^2 arr[2]+12 为最优解。
重复步骤。。。
遍历完整个数组的长度之后,整个数组都会被填充完成,因此直接返回 arr[n] 即可。
DP 的代码会比 BFS 的简单一些,需要注意的就是外层的 for 循环为 [1, 2, ..., n],第二层循环体的条件为 [1, ..., i]。
第二层循环为了获取完美平方数,因此当完美平方数大于当前值的时候,就可以跳出这一层循环了。
nums[i] = Math.min(nums[i], 1 + nums[i - squareVal]); 是为了更新当前所能获取的最小值。
/**
* @param {number} n
* @return {number}
*/
var numSquares = function (n) {
const nums = new Array(n + 1).fill(n);
nums[0] = 0;
for (let i = 1; i <= n; i++) {
for (let j = 1; j < i + 1; j++) {
const squareVal = j * j;
if (squareVal > i) break;
nums[i] = Math.min(nums[i], 1 + nums[i - squareVal]);
squareVal;
}
}
return nums[n];
};
console.log(numSquares(3));
console.log(numSquares(4));
console.log(numSquares(12));
console.log(numSquares(13));
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)