繁体   English   中英

在对每个元素的数字求和后重建一个整数数组

[英]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,因此我们可以限制算法搜索的死亡。

我想到的一种可能更快找到答案的方法是:

  1. 从新的 arrays 中的最后一个元素开始,找到所有可能的数字,它们的数字总和是这个元素的结果。
  2. 然后尝试在最后一步中为该元素使用最小量。
  3. 现在尝试对倒数第二个元素执行相同的操作。 如果为倒数第二个元素找到的最小排列值大于为最后一个元素找到的最小排列值,则回溯到最后一个元素并尝试更大的排列。
  4. 这样做直到到达第一个元素。

我认为这是一个贪婪的解决方案,但我不太确定时间复杂度。 另外我想知道这个问题有更好的解决方案吗? 喜欢用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) ,因为这主要dpprev表的大小。

我们可以使用贪心算法:按顺序遍历数组,将每个元素设置为大于前一个元素的最小值,并具有适当总和的数字。 (我们可以遍历所有可能的值并检查它们数字的总和。)没有必要考虑任何比这更大的值,因为增加给定元素永远不可能减少后面的元素。 所以我们这里不需要动态规划。

我们可以在 O(log m ) 时间内计算 integer m的数字总和,因此整个解决方案需要 O( b log b ) 时间,其中b是上限(在您的示例中为 500)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM