Leetcode 中的完美正方形
Posted
技术标签:
【中文标题】Leetcode 中的完美正方形【英文标题】:Perfect Square in Leetcode 【发布时间】:2016-12-26 03:30:13 【问题描述】:我无法理解 Leetcode 问题之一。
给定一个正整数 n,找出和为 n 的最小完美平方数(例如,1、4、9、16,...)。
例如,给定 n = 12,返回 3,因为 12 = 4 + 4 + 4;给定 n = 13,返回 2,因为 13 = 4 + 9。
解决方案:
int numSquares(int n)
static vector<int> dp 0;
while (dp.size() <= n)
int m = dp.size(), squares = INT_MAX;
for (int i=1; i*i<=m; ++i)
squares = min(squares, dp[m-i*i] + 1);
dp.push_back(squares);
return dp[n];
我真的不明白min(squares,dp[m-i*i]+1)
是怎么回事。能解释一下吗?
谢谢。
【问题讨论】:
究竟你不明白什么?是min
吗?是dp[m-i*i]+1
吗?是逻辑吗,作者为什么写这个?
我不明白在for循环中做min
人员的目的,我真的不明白这是如何解决问题的。
@randy 你刚刚写的那条评论应该是问题的一部分。请相应地edit。
【参考方案1】:
我也遇到了困难。让我们以数字 n=13 为例。
首先要观察的是:1^2 =1, 2^2=4, 3^2=9, 4^2=16 所以 13 不能由大于 3^2。一般而言,n只能由数字1到sqrt(n)组成 所以我们得到了以下数字的平方的某种组合:1、2 或 3。 接下来我们要做的是提出递归公式。这让我花了很长时间才明白。但是我们基本上想缩小以使用更小的 n (这就是递归的全部意义)。我们通过从 n 中减去我们的候选完美平方来做到这一点。例如: 如果我们尝试 3,则 dp(13)=dp(13-3^2)+1=dp(4)+1。 +1 将计数增加 1,这是因为我们已经从 13 中取出了一个完美的正方形,即 3^2。每个 +1 都是我们起飞的完美方格。 如果我们尝试 2,那么 dp(13)=13-2^2=dp(9)+1 如果我们尝试 1,那么 dp(13)=13-1^2=dp(12)+1所以我们要比较 dp(4)、dp(9) 和 dp(12) 中哪个最小。因此最小。
【讨论】:
非常感谢您的精彩解释。清除所有疑虑!【参考方案2】:您提到的解决方案是算法的自下而上版本。为了更好地理解算法,我建议查看自顶向下版本的解决方案。
让我们仔细看看用于计算完美平方的最小数量的递归关系,包含在数字N
中。对于给定的N
和任意数字x
(被认为是最短数字序列的成员,其完全平方和为N
):
f(N, x) = 0 , if N = 0 ;
f(N, x) = min( f(N, x + 1), f(N - x^2, 1) ) , if N >= x^2 ;
f(N, x) = +infinity , otherwise ;
solution(N) = f(N, 1)
现在,考虑到考虑的重复性,我们可以构建自上而下的解决方案(我将在 Java 中实现它):
int solve(int n)
return solve(n, 1);
int solve(int n, int curr)
if (n == 0)
return 0;
if ((curr * curr) > n)
return POSITIVE_INFINITY;
// if curr belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
int inclusive = solve(n - (curr * curr), 1) + 1;
// otherwise:
int exclusive = solve(n, curr + 1);
return Math.min(exclusive, inclusive);
给定解决方案的运行时复杂度是指数级的。
但是,我们可以注意到只有[1..n]
可能的值是n
和[1..sqrt(n)]
的值是curr
。这意味着只有n * sqrt(n)
函数solve
的不同参数值的组合。因此,我们可以创建记忆表并降低自顶向下解决方案的复杂性:
int solve(int n)
// initialization of the memoization table
int[][] memoized = new int[n + 1][(int) (Math.sqrt(n) + 1)];
for (int[] row : memoized)
Arrays.fill(row, NOT_INITIALIZED);
return solve(n, 1, memoized);
int solve(int n, int curr, int[][] memoized)
if (n == 0)
return 0;
if ((curr * curr) > n)
return POSITIVE_INFINITY;
if (memoized[n][curr] != NOT_INITIALIZED)
// the sub-problem has been already solved
return memoized[n][curr];
int exclusive = solve(n, curr + 1, memoized);
int inclusive = solve(n - (curr * curr), 1, memoized) + 1;
memoized[n][curr] = Math.min(exclusive, inclusive);
return memoized[n][curr];
给定的解决方案具有运行时复杂度O(N * sqrt(N))
。
但是,可以将运行时复杂度降低到O(N)
。
至于f(N, x)
的递归关系仅取决于f(N, x + 1)
和f(N - x^2, 1)
- 这意味着该关系可以等效地转换为循环形式:
f(0) = 0
f(N) = min( f(N - x^2) + 1 ) , across the all x, such that x^2 <= N
在这种情况下,我们必须记住 f(N)
仅用于 N
其参数的不同值。
因此,下面介绍了O(N)
自上而下的解决方案:
int solve_top_down_2(int n)
int[] memoized = new int[n + 1];
Arrays.fill(memoized, NOT_INITIALIZED);
return solve_top_down_2(n, memoized);
int solve_top_down_2(int n, int[] memoized)
if (n == 0)
return 0;
if (memoized[n] != NOT_INITIALIZED)
return memoized[n];
// if 1 belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
int result = solve_top_down_2(n - (1 * 1)) + 1;
for (int curr = 2; (curr * curr) <= n; curr++)
// check, whether some other number belongs to the shortest sequence of numbers, whose perfect squares sums-up to N
result = Math.min(result, solve_top_down_2(n - (curr * curr)) + 1);
memoized[n] = result;
return result;
最后,提出的自顶向下解决方案可以很容易地转换为自底向上解决方案:
int solve_bottom_up(int n)
int[] memoized = new int[n + 1];
for (int i = 1; i <= n; i++)
memoized[i] = memoized[i - (1 * 1)] + 1;
for (int curr = 2; (curr * curr) <= i; curr++)
memoized[i] = Math.min(memoized[i], memoized[i - (curr * curr)] + 1);
return memoized[n];
【讨论】:
【参考方案3】:对您的困惑的澄清在于问题本身。结构dp
包含最少数量的正方形,它们总和到dp
的索引位置。
例如,squares
将在 n=9
时返回 3
,但最不可能的是 1
,这是 dp[m- i*i] + 1
将返回的内容。
【讨论】:
以上是关于Leetcode 中的完美正方形的主要内容,如果未能解决你的问题,请参考以下文章