[279. 完全平方数]
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
1 2 3
| 输入:n = 12 输出:3 解释:12 = 4 + 4 + 4
|
示例 2:
1 2 3
| 输入:n = 13 输出:2 解释:13 = 4 + 9
|
可能刚看这种题感觉没啥思路,又平方和的,又最小数的。
我来把题目翻译一下:完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目动态规划:322. 零钱兑换就是一样一样的!
动规五部曲分析如下:
- 确定dp数组(dp table)以及下标的含义
dp[j]:和为j的完全平方数的最少数量为dp[j]
- 确定递推公式
dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。
此时我们要选择最小的dp[j],所以递推公式:dp[j] = min(dp[j - i * i] + 1, dp[j]);
- dp数组如何初始化
dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0。
有同学问题,那0 * 0 也算是一种啊,为啥dp[0] 就是 0呢?
看题目描述,找到若干个完全平方数(比如 1, 4, 9, 16, …),题目描述中可没说要从0开始,dp[0]=0完全是为了递推公式。
非0下标的dp[j]应该是多少呢?
从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[j]一定要初始为最大值,这样dp[j]在递推的时候才不会被初始值覆盖。
- 确定遍历顺序
我们知道这是完全背包,
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
在动态规划:322. 零钱兑换中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
所以本题外层for遍历背包,内层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的!
我这里先给出外层遍历背包,内层遍历物品的代码:
1 2 3 4 5 6 7
| vector<int> dp(n + 1, INT_MAX); dp[0] = 0; for (int i = 0; i <= n; i++) { for (int j = 1; j * j <= i; j++) { dp[i] = min(dp[i - j * j] + 1, dp[i]); } }
|
- 举例推导dp数组
已输入n为5例,dp状态图如下:
![image.png]()
dp[0] = 0
dp[1] = min(dp[0] + 1) = 1
dp[2] = min(dp[1] + 1) = 2
dp[3] = min(dp[2] + 1) = 3
dp[4] = min(dp[3] + 1, dp[0] + 1) = 1
dp[5] = min(dp[4] + 1, dp[1] + 1) = 2
最后的dp[n]为最终结果。
C++代码
以上动规五部曲分析完毕C++代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int numSquares(int n) { vector<int> dp(n + 1, INT_MAX); dp[0] = 0; for (int i = 0; i <= n; i++) { for (int j = 1; j * j <= i; j++) { dp[i] = min(dp[i - j * j] + 1, dp[i]); } } return dp[n]; } };
|
同样我在给出先遍历物品,在遍历背包的代码,一样的可以AC的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public: int numSquares(int n) { vector<int> dp(n + 1, INT_MAX); dp[0] = 0; for (int i = 1; i * i <= n; i++) { for (int j = i * i; j <= n; j++) { dp[j] = min(dp[j - i * i] + 1, dp[j]); } } return dp[n]; } };
|
总结
如果大家认真做了昨天的题目动态规划:322. 零钱兑换,今天这道就非常简单了,一样的套路一样的味道。
但如果没有按照「代码随想录」的题目顺序来做的话,做动态规划或者做背包问题,上来就做这道题,那还是挺难的!
经过前面的训练这道题已经是简单题了,哈哈哈
## 其他语言版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
| class Solution { public int numSquares(int n) { int max = Integer.MAX_VALUE; int[] dp = new int[n + 1]; for (int j = 0; j <= n; j++) { dp[j] = max; } dp[0] = 0; for (int i = 1; i * i <= n; i++) { for (int j = i * i; j <= n; j++) { if (dp[j - i * i] != max) { dp[j] = Math.min(dp[j], dp[j - i * i] + 1); } } } return dp[n]; } }
class Solution { public int numSquares(int n) { int max = Integer.MAX_VALUE; int[] dp = new int[n + 1]; for (int j = 0; j <= n; j++) { dp[j] = max; } dp[0] = 0; for (int j = 1; j <= n; j++) { for (int i = 1; i * i <= j; i++) { dp[j] = Math.min(dp[j], dp[j - i * i] + 1); } } return dp[n]; } } class Solution: def numSquares(self, n: int) -> int: '''版本一,先遍历背包, 再遍历物品''' # 初始化 nums = [i**2 for i in range(1, n + 1) if i**2 <= n] dp = [10**4]*(n + 1) dp[0] = 0 # 遍历背包 for j in range(1, n + 1): # 遍历物品 for num in nums: if j >= num: dp[j] = min(dp[j], dp[j - num] + 1) return dp[n] def numSquares1(self, n: int) -> int: '''版本二, 先遍历物品, 再遍历背包''' # 初始化 nums = [i**2 for i in range(1, n + 1) if i**2 <= n] dp = [10**4]*(n + 1) dp[0] = 0 # 遍历物品 for num in nums: # 遍历背包 for j in range(num, n + 1): dp[j] = min(dp[j], dp[j - num] + 1) return dp[n]
func numSquares1(n int) int { dp := make([]int, n+1) dp[0] = 0 for i := 1; i <= n; i++ { dp[i] = math.MaxInt32 } for i := 1; i <= n; i++ { for j := i*i; j <= n; j++ { dp[j] = min(dp[j], dp[j-i*i]+1) } }
return dp[n] }
func numSquares2(n int) int { dp := make([]int, n+1) dp[0] = 0 for j := 1; j <= n; j++ { dp[j] = math.MaxInt32 for i := 1; i <= n; i++ { if j >= i*i { dp[j] = min(dp[j], dp[j-i*i]+1) } } }
return dp[n] }
func min(a, b int) int { if a < b { return a } return b }
var numSquares1 = function(n) { let dp = new Array(n + 1).fill(Infinity) dp[0] = 0
for(let i = 1; i**2 <= n; i++) { let val = i**2 for(let j = val; j <= n; j++) { dp[j] = Math.min(dp[j], dp[j - val] + 1) } } return dp[n] };
var numSquares2 = function(n) { let dp = new Array(n + 1).fill(Infinity) dp[0] = 0
for(let i = 1; i <= n; i++) { for(let j = 1; j * j <= i; j++) { dp[i] = Math.min(dp[i - j * j] + 1, dp[i]) } }
return dp[n] }; function numSquares(n: number): number { const goodsNum: number = Math.floor(Math.sqrt(n)); const dp: number[] = new Array(n + 1).fill(Infinity); dp[0] = 0; for (let i = 1; i <= goodsNum; i++) { const tempVal: number = i * i; for (let j = tempVal; j <= n; j++) { dp[j] = Math.min(dp[j], dp[j - tempVal] + 1); } } return dp[n]; };
|