使用线性递归合并/乘法数组中的元素

zz2j4svz  于 2021-07-08  发布在  Java
关注(0)|答案(1)|浏览(347)

我必须实现一个递归方法 merge(long[] arr, int i) 如果相邻元素具有相同的值,则从索引开始将其相乘 i . 例子:

  1. merge({1, 2, 2, 4}, 0)

应该生成如下数组:

  1. {1, 4, 4}

如果一个数字有多个(n)次出现 {1, 2, 2, 2, 2, 5} ,所有这些必须相乘: {1, 16, 5} .
已经合并的号码不能再合并 {1, 4, 4, 16} -> {1, 16, 16} .
所有这些都必须通过只使用一个方法merge来实现,并且在原始数组中每个元素只有一个递归调用。
这是一个使用递归和循环的工作实现:

  1. public static long[] merge(long[] ns, int i) {
  2. final long[] EMPTY_LONG_ARRAY = {};
  3. if (i < 0) {
  4. return merge(ns, 0, m); // if i negative, start at 0
  5. } else if (i >= ns.length) {
  6. return EMPTY_LONG_ARRAY; // if out of bounds, return empty array
  7. } else if (i == ns.length - 1) {
  8. return ns; // base case
  9. } else { // recursion in here
  10. if (ns[i] == ns[i + 1]) { // if next long is equal
  11. int occurences = 1; // first occurence
  12. for (int j = i; j < ns.length - 1; j++) {
  13. if (ns[j] == ns[j + 1])
  14. occurences++;
  15. else
  16. break;
  17. } // add next occurences
  18. long[] newArray = new long[ns.length - occurences + 1]; // new array is (occurences-1) shorter
  19. for (int j = 0; j < newArray.length; j++) { // fill new array
  20. if (j < i) {
  21. newArray[j] = ns[j]; // left of i: values stay the same
  22. } else if (j > i) {
  23. newArray[j] = ns[j + occurences - 1]; // pull values right of i (occurences-1) to the left
  24. } else {
  25. int counter = occurences;
  26. long mergedValue = ns[j];
  27. while (counter > 1) {
  28. mergedValue *= ns[j];
  29. counter--;
  30. }
  31. newArray[j] = mergedValue; // at j: value is ns[j]^occurences
  32. }
  33. }
  34. if (i == ns.length - 1)
  35. return merge(newArray, i, m);
  36. else
  37. return merge(newArray, i + 1, m); // if bounds permit it, jump to next number
  38. } else {
  39. return merge(ns, i + 1, m); // nothing to merge, go one step forward
  40. }
  41. }

这个实现产生正确的结果,但是递归深度是错误的(在原始数组ns[]中每个元素需要一个递归调用)。
我肯定有个天才可以用线性递归来解决这个问题。

6tqwzwtp

6tqwzwtp1#

让我们将循环转换为递归调用。这样做的唯一原因是作业要求它-它不是更具可读性(至少对我来说是这样),而且它实际上更慢。出于效率的考虑,人们通常希望转向另一个方向:从递归到循环。
首先,代码的注解版本:

  1. public static long[] merge(long[] ns, int i) { // i not needed, but useful for recursion
  2. long[] out = new long[ns.length]; // for result; allocate only once
  3. for (int j = i; j < ns.length; j++) { // main loop, condition is "j == length"
  4. int occurences = 0;
  5. for (int k = i; k < ns.length; k++) { // inner loop - can avoid!
  6. if (ns[j] == ns[k]) {
  7. occurences++;
  8. }
  9. }
  10. out[j] = (long) Math.pow(ns[j], occurences); // updating the result
  11. }
  12. // remove additional elements
  13. return out; // this does not remove elements yet!
  14. }

首先,让我重写一下,提高效率。由于重复项只有在相邻时才被删除,因此不需要内部循环,可以编写以下内容:

  1. public static long[] merge(long[] ns) {
  2. long[] out = new long[ns.length];
  3. int oSize = 0; // index of element-after-last in array out
  4. long prev = ns[0]; // previous element in ns; initial value chosen carefully
  5. out[0] = 1; // this make the 1st iteration work right, not incrasing oSize
  6. for (int i=0; i<ns.length; i++) {
  7. long current = ns[i];
  8. if (current == prev) {
  9. out[oSize] *= current; // accumulate into current position
  10. } else {
  11. oSize ++; // generate output
  12. out[oSize] = current; // reset current and prev
  13. prev = current;
  14. }
  15. }
  16. // generate final output, but do not include unused elements
  17. return Arrays.copyOfRange(out, 0, oSize+1);
  18. }

假设这是可行的(注意-我还没有测试过),我现在将把它转换成尾部递归。将有两部分:驱动程序代码(不在循环中的所有内容)和递归代码(循环部分)。

  1. public static long[] merge(long[] ns) {
  2. long[] out = new long[ns.length];
  3. int oSize = 0;
  4. long prev = ns[0];
  5. out[0] = 1;
  6. int i=0;
  7. recursiveMerge(ns, i, out, oSize, prev); // recursion!
  8. return Arrays.copyOfRange(out, 0, oSize+1);
  9. }
  10. public static void recursiveMerge(long[] ns, int i, long[] out, int oSize, long prev) {
  11. if (i == n) return; // check "loop" termination condition
  12. // copy-pasted loop contents
  13. long current = ns[i];
  14. if (current == prev) {
  15. out[oSize] *= current; // accumulate into current position
  16. } else {
  17. oSize ++; // generate output
  18. out[oSize] = current; // reset current and prev
  19. prev = current;
  20. }
  21. // next loop iteration is now a recursive call. Note the i+1
  22. recursiveMerge(ns, i+1, out, oSize, prev);
  23. }

一般的想法是将所有状态作为参数传递给递归函数,并在开始时检查循环终止,将循环代码放在中间,最后为下一次迭代进行递归调用。

展开查看全部

相关问题