您提到的解决方案是算法的自下而上版本。为了更好地理解算法,我建议查看自顶向下版本的解决方案。
让我们仔细看看计算包含在 number 中的最少量的完美平方的递归关系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];
}