为简单起见,让我们的序列基于1,输入序列称为x。
我们还将使用一个实用函数,它返回给定数字的位数之和:
int sum(int x) {
int result = 0;
while (x > 0) {
result += x % 10;
x /= 10;
}
return result;
}
假设我们位于索引idx 并尝试在那里设置一个名为value 的数字(假设value 的数字总和为x[idx])。如果我们这样做,那么对于序列中的前一个数字我们能说什么?它应该严格小于value。
所以我们已经有了一个潜在的dp 方法的状态 - [idx, value],其中idx 是我们当前所在的索引,value 表示我们试图在这个索引上设置的值。
如果dp 表包含boolean 值,如果我们为序列中的第一个数字找到了合适的数字,我们就知道我们已经找到了答案。因此,如果有一个路径,从dp 表的最后一行开始,到0 行结束,那么我们就知道我们找到了答案,然后我们可以简单地恢复它。
我们的循环函数将是这样的:
f(idx, value) = OR {dp[idx - 1][value'], where sumOfDigits(value) = x[idx] and value' < value}
f(0, *) = true
另外,为了恢复答案,我们需要跟踪路径。一旦我们将任何dp[idx][value] 单元格设置为真,那么我们就可以保护上一个表格行中我们想要跳转的value'。
现在让我们编写代码。我希望代码是不言自明的:
boolean[][] dp = new boolean[n + 1][501];
int[][] prev = new int[n + 1][501];
for (int i = 0; i <= 500; i++) {
dp[0][i] = true;
}
for (int idx = 1; idx <= n; idx++) {
for (int value = 1; value <= 500; value++) {
if (sum(value) == x[idx]) {
for (int smaller = 0; smaller < value; smaller++) {
dp[idx][value] |= dp[idx - 1][smaller];
if (dp[idx][value]) {
prev[idx][value] = smaller;
break;
}
}
}
}
}
prev 表仅保留有关哪个是最小 value' 的信息,我们可以在结果序列中将其用作idx 的前一个。
现在,为了恢复序列,我们可以从最后一个元素开始。我们希望它是最小的,所以我们可以找到第一个有dp[n][value] = true 的。一旦我们有了这样的元素,我们就可以使用prev 表来追踪直到第一个元素的值:
int[] result = new int[n];
int idx = n - 1;
for (int i = 0; i <= 500; i++) {
if (dp[n][i]) {
int row = n, col = i;
while (row > 0) {
result[idx--] = col;
col = prev[row][col];
row--;
}
break;
}
}
for (int i = 0; i < n; i++) {
out.print(result[i]);
out.print(' ');
}
如果我们将其应用于输入序列:
3 2 5 10 9 6
我们得到
3 11 14 19 27 33
时间复杂度为O(n * m * m),其中n 是我们拥有的元素数量,m 是一个元素可以容纳的最大可能值。
空间复杂度为O(n * m),因为这取决于dp 和prev 表的大小。