leetcode-master/problems/0279.完全平方数.md

486 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://www.programmercarl.com/xunlian/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>
# 279.完全平方数
[力扣题目链接](https://leetcode.cn/problems/perfect-squares/)
给定正整数 n找到若干个完全平方数比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数其值等于另一个整数的平方换句话说其值等于一个整数自乘的积。例如1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1
* 输入n = 12
* 输出3
* 解释12 = 4 + 4 + 4
示例 2
* 输入n = 13
* 输出2
* 解释13 = 4 + 9
提示:
* 1 <= n <= 10^4
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[换汤不换药!| LeetCode279.完全平方数](https://www.bilibili.com/video/BV12P411T7Br/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
## 思路
可能刚看这种题感觉没啥思路,又平方和的,又最小数的。
**我来把题目翻译一下完全平方数就是物品可以无限件使用凑个正整数n就是背包问凑满这个背包最少有多少物品**
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)就是一样一样的!
动规五部曲分析如下:
1. 确定dp数组dp table以及下标的含义
**dp[j]和为j的完全平方数的最少数量为dp[j]**
2. 确定递推公式
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]);
3. 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]在递推的时候才不会被初始值覆盖**。
4. 确定遍历顺序
我们知道这是完全背包,
如果求组合数就是外层for循环遍历物品内层for遍历背包。
如果求排列数就是外层for遍历背包内层for循环遍历物品。
在[动态规划322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
**所以本题外层for遍历背包内层for遍历物品还是外层for遍历物品内层for遍历背包都是可以的**
我这里先给出外层遍历背包,内层遍历物品的代码:
```CPP
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]);
}
}
```
5. 举例推导dp数组
已输入n为5例dp状态图如下
![279.完全平方数](https://code-thinking-1253855093.file.myqcloud.com/pics/20210202112617341.jpg)
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++代码如下:
```CPP
// 版本一
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];
}
};
```
* 时间复杂度: O(n * √n)
* 空间复杂度: O(n)
同样我在给出先遍历物品在遍历背包的代码一样的可以AC的。
```CPP
// 版本二
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. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html),今天这道就非常简单了,一样的套路一样的味道。
但如果没有按照「代码随想录」的题目顺序来做的话,做动态规划或者做背包问题,上来就做这道题,那还是挺难的!
经过前面的训练这道题已经是简单题了
## 其他语言版本
### Java
```Java
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;
}
//如果不想要寫for-loop填充數組的話也可以用JAVA內建的Arrays.fill()函數。
//Arrays.fill(dp, Integer.MAX_VALUE);
//当和为0时组合的个数为0
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);
//}
//不需要這個if statement因爲在完全平方數這一題不會有"湊不成"的狀況發生( 一定可以用"1"來組成任何一個n故comment掉這個if statement。
}
}
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;
}
// 当和为0时组合的个数为0
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];
}
}
```
### Python
先遍历背包, 再遍历物品
```python
class Solution:
def numSquares(self, n: int) -> int:
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1): # 遍历背包
for j in range(1, int(i ** 0.5) + 1): # 遍历物品
# 更新凑成数字 i 所需的最少完全平方数数量
dp[i] = min(dp[i], dp[i - j * j] + 1)
return dp[n]
```
先遍历物品, 再遍历背包
```python
class Solution:
def numSquares(self, n: int) -> int:
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, int(n ** 0.5) + 1): # 遍历物品
for j in range(i * i, n + 1): # 遍历背包
# 更新凑成数字 j 所需的最少完全平方数数量
dp[j] = min(dp[j - i * i] + 1, dp[j])
return dp[n]
```
其他版本
```python
class Solution:
def numSquares(self, n: int) -> int:
# 创建动态规划数组,初始值为最大值
dp = [float('inf')] * (n + 1)
# 初始化已知情况
dp[0] = 0
# 遍历背包容量
for i in range(1, n + 1):
# 遍历完全平方数作为物品
j = 1
while j * j <= i:
# 更新最少完全平方数的数量
dp[i] = min(dp[i], dp[i - j * j] + 1)
j += 1
# 返回结果
return dp[n]
```
```python
class Solution(object):
def numSquares(self, n):
# 先把可以选的数准备好,更好理解
nums, num = [], 1
while num ** 2 <= n:
nums.append(num ** 2)
num += 1
# dp数组初始化
dp = [float('inf')] * (n + 1)
dp[0] = 0
# 遍历准备好的完全平方数
for i in range(len(nums)):
# 遍历背包容量
for j in range(nums[i], n+1):
dp[j] = min(dp[j], dp[j-nums[i]]+1)
# 返回结果
return dp[-1]
```
### Go
```go
// 版本一,先遍历物品, 再遍历背包
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
}
```
### JavaScript:
```Javascript
// 先遍历物品,再遍历背包
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]
};
```
### TypeScript
```typescript
// 先遍历物品
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];
};
```
```rust
// 先遍历背包
function numSquares(n: number): number {
const dp = 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], dp[i -j * j] + 1)
}
}
return dp[n]
};
```
### C
```c
#define min(a, b) ((a) > (b) ? (b) : (a))
int numSquares(int n) {
int* dp = (int*)malloc(sizeof(int) * (n + 1));
for (int j = 0; j < n + 1; j++) {
dp[j] = 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];
}
```
### Rust:
```rust
// 先遍历背包
impl Solution {
pub fn num_squares(n: i32) -> i32 {
let n = n as usize;
let mut dp = vec![i32::MAX; n + 1];
dp[0] = 0;
for i in 0..=n {
let mut j = 1;
loop {
match j * j > i {
true => break,
false => dp[i] = dp[i].min(dp[i - j * j] + 1),
}
j += 1;
}
}
dp[n]
}
}
```
```rust
// 先遍历物品
impl Solution {
pub fn num_squares(n: i32) -> i32 {
let (n, mut goods) = (n as usize, 1);
let mut dp = vec![i32::MAX; n + 1];
dp[0] = 0;
loop {
if goods * goods > n {
break;
}
for j in goods * goods..=n {
dp[j] = dp[j].min(dp[j - goods * goods] + 1);
}
goods += 1;
}
dp[n]
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>