动态规划 - 279.完全平⽅数

328 阅读2分钟

4月日新计划更文活动 第17天

前言

动态规划专题,从简到难通关动态规划。

每日一题

今天的题目是 279. 完全平方数,难度为中等

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入: n = 12 输出: 3 解释:12 = 4 + 4 + 4

示例 2:

输入: n = 13 输出: 2 解释: 13 = 4 + 9

 

提示:

  • 1 <= n <= 104

题解

将题⽬换一种说法来理解,可能就会比较容易一点:完全平⽅数可以当做是物品(可以⽆限件使⽤),凑个正整数n就是背包,问凑满这个背包最少要多少物品

  1. 定义dp数组以及下标含义:

dp[i] 表示和为 i 的完全平方数的最少数量

  1. 确定递推公式

对于本题,我们需要求和为 i 的最少完全平方数的数量 dp[i],而每个完全平方数可以表示成 j * j 的形式,其中 j 为整数。

那么对于某一个 i,我们需要寻找一个完全平方数 j * j,使得 i - j * j 之前的结果最小(即 dp[i - j * j]),然后再加上 j * j 这一个完全平方数,即可得到和为 i 的最少完全平方数的数量。因此,递推公式为:

dp[i] = Math.min(dp[i], dp[i - j * j] + 1) 

其中 j 需要从 1 遍历到 Math.floor(Math.sqrt(i)),因为 j * j <= i,即 j <= Math.sqrt(i)。

  1. dp数组初始化

dp[0]表⽰ 和为0的完全平⽅数的最⼩数量,那么dp[0]⼀定是0。因为题目说了 从 1 开始,并不包含 0.

然后根据递推公式的含义,我们需要把dp数组的每一项都初始化为最大值,使用 Number.MAX_SAFE_INTEGER 来初始化dp数组。

  1. 确定遍历顺序

在完全背包当中,

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

如果求排列数就是外层for遍历背包,内层for循环遍历物品。

在遍历 dp 数组时,对于每一个 i,需要枚举所有小于等于它的完全平方数 j*j

  1. 举例推导dp数组
i012345
dp[i]012312
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

代码:

function numSquares(n: number): number {
  const dp = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER)
  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]
};

image.png