【问题标题】:Rebuild an array of integers after summing the digits of each element在对每个元素的数字求和后重建一个整数数组
【发布时间】:2021-02-23 03:47:27
【问题描述】:

我们有一个长度严格递增的数组 n ( 1

3 11 23 37 45 123 =>3 2 5 10 9 6

现在从第二个数组,我们可以用许多不同的方式重建原始数组,例如:

12 20 23 37 54 60

从所有可能的组合中,我们需要一个我们最小的最后一个元素。

到目前为止我的想法:

蛮力方法是找到所有可能的排列来创建每个数字,然后创建第二个数组的所有数字的所有可能组合,并找到具有最小最后一个元素的组合。这显然不是一个好的选择。

使用this 算法(具有指数时间!)我们可以创建所有可能的数字排列,这些排列总和为第二个数组中的一个数字。请注意,我们知道原始元素少于 500,因此我们可以限制算法搜索的死亡。

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

  1. 从新数组的最后一个元素开始,找出所有可能的 他们的数字总和导致这个元素的数字。
  2. 然后尝试在最后一步中为这个元素使用最少的数量。
  3. 现在尝试对倒数第二个元素执行相同的操作。如果 为倒数第二个元素找到的最小排列值更大 比为最后一个元素找到的那个,回溯到最后一个 元素并尝试更大的排列。
  4. 执行此操作,直到到达第一个元素。

我认为这是一个贪婪的解决方案,但我不太确定时间复杂度。另外我想知道这个问题有更好的解决方案吗?喜欢使用dp

【问题讨论】:

  • 我认为你可以在O(n ^ 3) 中使用动态编程来实现
  • @KonstantinYovkov 你能解释更多吗?
  • 我很好奇这个算法的应用?

标签: arrays algorithm


【解决方案1】:

为简单起见,让我们的序列基于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),因为这取决于dpprev 表的大小。

【讨论】:

  • 谢谢,但我认为有问题。问题是新数组中的每个值最大为 500。这意味着原始数组可以有非常大的数字。
  • @FrastoFresto:哇,差别很大!
  • @FrastoFresto,为了获得[1,500] 范围内的值,那么输入数组的值应不超过22(如sumOfDigits(499) = 22,这是我们可以获得的最大值[1,500] 范围内数字的数字总和)。因此,如果您的输入数组包含更大的值,则无法获取[1,500] 范围内的值
  • @KonstantinYovkov 好的,如果输入数组包含 500 大的数字怎么办?算法如何变化?
  • @FrastoFresto,我会考虑并进一步发表评论。
【解决方案2】:

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

我们可以计算一个整数m在O(log m)时间内的位数之和,所以整个解需要O(b em> log b) 时间,其中 b 是上限(在您的示例中为 500)。

【讨论】:

  • 我不明白你的复杂性评估。我知道 500 是对数字求和后获得的最大值,而不是最终数组中的最大值。我可能会错过一些东西。我同意贪婪的方法。
  • @Damien:问题已编辑;在我写这个答案的时候,它指定 500 是 之前 对数字求和的最大值。我还没有机会相应地改变答案。 (“仅迭代可能的值”部分不再合理。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-15
  • 1970-01-01
相关资源
最近更新 更多