[英]Rebuild an array of integers after summing the digits of each element
我们有一个长度为n
(1 < n < 500)的严格递增数组。 我们将每个元素的数字相加以创建一个新数组,每个元素的值在 1 到 500 之间。任务是从新数组重建旧数组。 由于可能有多个答案,我们希望答案具有最后一个元素的最小值。 例子:
3 11 23 37 45 123 =>3 2 5 10 9 6
现在从第二个数组,我们可以用许多不同的方式重建原始数组,例如:
12 20 23 37 54 60
从所有可能的组合中,我们需要最小化最后一个元素。
到目前为止我的想法:
蛮力方法是找到所有可能的排列来创建每个数字,然后创建第二个数组的所有数字的所有可能组合,并找到最后一个元素最少的组合。 很明显,这不是一个好的选择。
使用此算法(使用指数时间),我们可以创建所有可能的数字排列,总和为第二个数字 arrays。请注意,我们知道原始元素少于 500,因此我们可以限制算法搜索的死亡。
我想到的一种可能更快找到答案的方法是:
我认为这是一个贪婪的解决方案,但我不太确定时间复杂度。 另外我想知道这个问题有更好的解决方案吗? 喜欢用dp
吗?
为简单起见,让我们以序列1
为基础,输入序列称为x
。
我们还将使用实用程序 function,它返回给定数字的数字总和:
int sum(int x) {
int result = 0;
while (x > 0) {
result += x % 10;
x /= 10;
}
return result;
}
让我们假设我们在索引idx
并尝试在那里设置一些称为value
的数字(假设value
的数字总和为x[idx]
)。 如果我们这样做,那么对于序列中的前一个数字我们能说些什么呢? 它应该严格小于value
。
所以我们已经有一个 state 用于潜在的dp
方法 - [idx, value]
,其中idx
是我们当前所在的索引, value
表示我们试图在此索引上设置的值。
如果dp
表包含boolean
值,如果我们为序列中的第一个数字找到了合适的数字,我们就会知道我们已经找到了答案。 因此,如果有一条路径从dp
表的最后一行开始到第0
行结束,那么我们就会知道我们找到了答案,然后我们可以简单地恢复它。
我们的循环 function 将是这样的:
f(idx, value) = OR {dp[idx - 1][value'], where sumOfDigits(value) = x[idx] and value' < value}
f(0, *) = true
此外,为了恢复答案,我们需要跟踪路径。 一旦我们将任何dp[idx][value]
单元格设置为 true,那么我们就可以保护我们想要在前一个表行中跳转到的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
表的大小。
我们可以使用贪心算法:按顺序遍历数组,将每个元素设置为大于前一个元素的最小值,并具有适当总和的数字。 (我们可以遍历所有可能的值并检查它们数字的总和。)没有必要考虑任何比这更大的值,因为增加给定元素永远不可能减少后面的元素。 所以我们这里不需要动态规划。
我们可以在 O(log m ) 时间内计算 integer m的数字总和,因此整个解决方案需要 O( b log b ) 时间,其中b是上限(在您的示例中为 500)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.