简体   繁体   English

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

[英]Rebuild an array of integers after summing the digits of each element

We have an strictly increasing array of length n ( 1 < n < 500).我们有一个长度为n (1 < n < 500)的严格递增数组。 We sum the digits of each element to create a new array with each elements values is in range 1 to 500.The task is to rebuild the old array from the new one.我们将每个元素的数字相加以创建一个新数组,每个元素的值在 1 到 500 之间。任务是从新数组重建旧数组。 since there might be more than one answer, we want the answers with the minimum value of the last element.由于可能有多个答案,我们希望答案具有最后一个元素的最小值。 Example:例子:

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

now from the second array, we can rebuild the original array in many different ways for instance:现在从第二个数组,我们可以用许多不同的方式重建原始数组,例如:

12 20 23 37 54 60 12 20 23 37 54 60

from all the possible combinations, we need the one we minimum last element.从所有可能的组合中,我们需要最小化最后一个元素。

My Thoughts so far:到目前为止我的想法:

The brute force way is to find all possible permutations to create each number and then create all combinations possible of all numbers of the second array and find the combination with minimum last element.蛮力方法是找到所有可能的排列来创建每个数字,然后创建第二个数组的所有数字的所有可能组合,并找到最后一个元素最少的组合。 It is obvious that this is not a good choice.很明显,这不是一个好的选择。

Using this algorithm(with exponential time.) we can create all possible permutations of digits that sum to a number in the second arrays. Note that we know the original elements were less than 500 so we can limit the death of search of the algorithm.使用算法(使用指数时间),我们可以创建所有可能的数字排列,总和为第二个数字 arrays。请注意,我们知道原始元素少于 500,因此我们可以限制算法搜索的死亡。

One way I thought of that might find the answer faster is to:我想到的一种可能更快找到答案的方法是:

  1. start from the last element in the new arrays and find all possible numbers that their digit sum resulted this element.从新的 arrays 中的最后一个元素开始,找到所有可能的数字,它们的数字总和是这个元素的结果。
  2. Then try to use the smallest amount in the last step for this element.然后尝试在最后一步中为该元素使用最小量。
  3. Now try to do the same with the second to last element.现在尝试对倒数第二个元素执行相同的操作。 If the minimum permutation value found for the second to last element is bigger than the one found for the last element, backtrack to the last element and try a larger permutation.如果为倒数第二个元素找到的最小排列值大于为最后一个元素找到的最小排列值,则回溯到最后一个元素并尝试更大的排列。
  4. Do this until you get to the first element.这样做直到到达第一个元素。

I think this is a greed solution but I'm not very sure about the time complexity.我认为这是一个贪婪的解决方案,但我不太确定时间复杂度。 Also I want to know is there a better solution for this problem?另外我想知道这个问题有更好的解决方案吗? like using dp ?喜欢用dp吗?

For simplicity, let's have our sequence 1 -based and the input sequence is called x .为简单起见,让我们以序列1为基础,输入序列称为x

We will also use an utility function, which returns the sum of the digits of a given number:我们还将使用实用程序 function,它返回给定数字的数字总和:

int sum(int x) {
  int result = 0;
  while (x > 0) {
    result += x % 10;
    x /= 10;
  }
  return result;
}

Let's assume that we are at index idx and try to set there some number called value (given that the sum of digits of value is x[idx] ).让我们假设我们在索引idx并尝试在那里设置一些称为value的数字(假设value的数字总和为x[idx] )。 If we do so, then what could we say about the previous number in the sequence?如果我们这样做,那么对于序列中的前一个数字我们能说些什么呢? It should be strictly less than value .它应该严格小于value

So we already have a state for a potential dp approach - [idx, value] , where idx is the index where we are currently at and value denotes the value we are trying to set on this index.所以我们已经有一个 state 用于潜在的dp方法 - [idx, value] ,其中idx是我们当前所在的索引, value表示我们试图在此索引上设置的值。

If the dp table holds boolean values, we will know we have found an answer if we have found a suitable number for the first number in the sequence.如果dp表包含boolean值,如果我们为序列中的第一个数字找到了合适的数字,我们就会知道我们已经找到了答案。 Therefore, if there is a path starting from the last row in the dp table and ends at row 0 then we'll know we have found an answer and we could then simply restore it.因此,如果有一条路径dp表的最后一行开始到第0行结束,那么我们就会知道我们找到了答案,然后我们可以简单地恢复它。

Our recurrence function will be something like this:我们的循环 function 将是这样的:

f(idx, value) = OR {dp[idx - 1][value'], where sumOfDigits(value) = x[idx] and value' < value}
f(0, *) = true

Also, in order to restore the answer, we need to track the path.此外,为了恢复答案,我们需要跟踪路径。 Once we set any dp[idx][value] cell to be true, then we can safe the value' to which we would like to jump in the previous table row.一旦我们将任何dp[idx][value]单元格设置为 true,那么我们就可以保护我们想要在前一个表行中跳转到的value'

Now let's code that one.现在让我们编写代码。 I hope the code is self-explanatory:我希望代码是不言自明的:

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;
        }
      }
    }
  }
}

The prev table only keeps information about which is the smallest value' , which we can use as previous to our idx in the resulting sequence. prev表只保留关于哪个是最小值value'信息,我们可以在结果序列中将其用作我们的idx之前的信息。

Now, in order to restore the sequence, we can start from the last element.现在,为了恢复序列,我们可以从最后一个元素开始。 We would like it to be minimal, so we can find the first one that has dp[n][value] = true .我们希望它是最小的,所以我们可以找到第一个dp[n][value] = true Once we have such element, we then use the prev table to track down the values up to the first one:一旦我们有了这样的元素,我们就可以使用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(' ');
}

If we apply this on an input sequence:如果我们将其应用于输入序列:

3 2 5 10 9 6

we get我们得到

3 11 14 19 27 33 

The time complexity is O(n * m * m) , where n is the number of elements we have and m is the maximum possible value that an element could hold.时间复杂度为O(n * m * m) ,其中n是我们拥有的元素数量, m是元素可以容纳的最大可能值。

The space complexity is O(n * m) as this is dominated by the size of the dp and prev tables.空间复杂度为O(n * m) ,因为这主要dpprev表的大小。

We can use a greedy algorithm: proceed through the array in order, setting each element to the least value that is greater than the previous element and has digits with the appropriate sum.我们可以使用贪心算法:按顺序遍历数组,将每个元素设置为大于前一个元素的最小值,并具有适当总和的数字。 (We can just iterate over the possible values and check the sums of their digits.) There's no need to consider any greater value than that, because increasing a given element will never make it possible to decrease a later element. (我们可以遍历所有可能的值并检查它们数字的总和。)没有必要考虑任何比这更大的值,因为增加给定元素永远不可能减少后面的元素。 So we don't need dynamic programming here.所以我们这里不需要动态规划。

We can calculate the sum of the digits of an integer m in O(log m ) time, so the whole solution takes O( b log b ) time, where b is the upper bound (500 in your example).我们可以在 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