【问题标题】:Find maximum product using recursion使用递归找到最大产品
【发布时间】:2020-07-17 22:32:25
【问题描述】:

我看到了一个问题,我想知道是否可以使用递归来解决它。如下:

编写一个算法,当给定一个输入数组时,从这些输入中找到最大乘积。例如:

Input: [1, 2, 3]
Output: 6 (1*2*3)
Input: [-1, 1, 2, 3]
Output: 6 (1*2*3)
Input: [-2, -1, 1, 2, 3]
Output: 12 (-2*-1*1*2*3)

我正在尝试找到一种使用递归来解决它的方法,但是我尝试的算法不起作用。我用Java编写的算法如下

Integer[] array;
public int maximumProduct(int[] nums) {
   array=new Integer[nums.length];
   return multiply(nums, 0);
}

public int multiply(int[] nums, int i){
    if (array[i]!=null){
        return array[i];
    }
    if (i==(nums.length-1)){
        return nums[i];
    }
    int returnval=Math.max(nums[i]*multiply(nums, i+1), multiply(nums, i+1));
    array[i]=returnval;
    return returnval;

}

这个算法的问题是,如果有偶数个负数,它就不能很好地工作。例如,如果 nums[0]=-2、nums[1]=-1 和 nums[2]=1,则 multiply(nums, 1) 将始终返回 1 而不是 -1,因此它始终会看到 1在 multiply(nums, 0) 处大于 1*-2。但是,我不确定如何解决这个问题。有没有办法使用递归或动态编程来解决这个问题?

【问题讨论】:

  • 如果你被限制为 int,计算负整数的个数。如果为奇数,则丢弃最小的负整数,否则将所有其他整数相乘以获得最大的乘积。首先删除所有零。不需要递归。
  • 是递归还是双for循环也可以?
  • 我希望使用递归,只是因为我想通过递归来提高我的技能,这一直是我的弱点。

标签: java algorithm recursion dynamic-programming divide-and-conquer


【解决方案1】:

如果数组中只有一个非零元素,并且它恰好是一个负数,那么答案是 0,如果输入中存在 0,或者如果数组只包含那个否定元素,答案就是那个元素本身。

在所有其他情况下,最终答案都是肯定的。

我们首先进行线性扫描以找到负整数的个数。如果这个数字是偶数,那么答案是所有非零元素的乘积。如果有奇数个否定元素,我们需要从答案中去掉一个否定元素,这样答案就是肯定的。由于我们想要最大可能的答案,我们想要遗漏的数字应该具有尽可能小的绝对值。所以在所有的负数中,找到绝对值最小的那个,然后找到剩下的非零元素的乘积,应该就是答案了。

所有这些只需要对数组进行两次线性扫描,因此运行时间为 O(n)。

【讨论】:

  • 我把 OP 的“我不确定如何解决这个问题”表示他们无法解决整个问题,但在第二次阅读时,我猜他们指的是特定的他们在递归解决方案中发现的问题。我的错。我应该删除这个答案吗?不确定协议。
  • 不。就留着吧。有人会发现它很有用。
  • 如果需要使用递归,则需要两个函数,一个查找最小值,一个查找最大值或剩余元素。首先,让它在没有任何优化的情况下工作,例如小于一、负数等,同时调用最小值和最大值,看看是否乘以最小值和最大值,这四个中哪个给出最大值。最少做类似(但相反)。
【解决方案2】:

整数的最大乘积是多少?

要获得最大和,您需要将所有正整数与最大负整数的乘积相乘,乘积中包含的负整数个数是偶数,以获得正的最终结果。

在单次遍历算法中

我将分别处理输入中的正整数和负整数。您需要保留正整数的运行积、负整数的运行积和迄今为止找到的最大负整数(即绝对值最小的负整数)。 让我们忽略最终答案为

//Initialization
int [] nums // Input
int posProduct = 1;
int negProduct = 1;
int smallestNeg = 1;

//Input Traversal
for (int i : nums) {
  if ( i == 0 ) {
    // ignore
  } else if ( i < 0 ) {
    if (smallestNeg == 1) {
      smallestNeg = i;
    } else if ( i > smallestNeg ) {
      negProduct *= smallestNeg; //Integrate the old smallest into the running product
      smallestNeg = i;           // i is the new smallest
    } else {
      negProduct *= i;
    }
  } else {
    // i is strictly positive
    posProduct *= i;
  }
}

//Result Computation
int result = posProduct;
if ( negProduct < 0 ) {
  // The running product of negative number numbers is negative
  // We use the smallestNeg to turn it back up to a positive product
  result *= smallestNeg;
  result *= negProduct;
} else {
  result *= negProduct
}

编辑:在递归遍历中

我个人发现以递归方式编写数组遍历很笨拙,但可以做到。 为了练习的美感并实际回答 OP 的问题,我将这样做。

public class RecursiveSolver {
  public static int findMaxProduct (int [] nums) {
    return recursiveArrayTraversal(1, 1, 1, nums, 0); 
  }

  private static int recursiveArrayTraversal(int posProduct, int negProduct, 
      int smallestNeg, int [] nums, int index) {
    if (index == nums.length) {
      // End of the recursion, we traversed the whole array
      posProduct *= negProduct;
      if (posProduct < 0) {
        posProduct *= smallestNeg;
      }
      return posProduct;
    }

    // Processing the "index" element of the array
    int i = nums[index];
    if ( i == 0 ) {
      // ignore
    } else if ( i < 0 ) {
      if (smallestNeg == 1) {
        smallestNeg = i;
      } else if ( i > smallestNeg ) {
        negProduct *= smallestNeg; 
        smallestNeg = i;
      } else {
        negProduct *= i;
      }
    } else {
      // i is strictly positive
      posProduct *= i;
    }

    //Recursive call here! 
    //Notice the index+1 for the index parameter which carries the progress 
    //in the array traversal
    return recursiveArrayTraversal(posProduct, negProduct, 
      smallestNeg, nums, index+1);
  }
}

【讨论】:

    【解决方案3】:

    首先,在子问题中打破数组,总是在列表中找到 0:

     1 -2  4 -1  8  0  4  1  0 -3 -4  0  1  3 -5
    |_____________|   |____|   |____|   |_______|
           p1           p2       p3         p4 
    

    然后,对于每个问题pi,计算其中有多少个负数。

    如果pi 有偶数个负数(或根本没有负数),则pi 的答案是其所有元素的乘积。

    如果pi只有1个负数(比如n),答案将是n右边所有元素的乘积与n中所有元素的乘积之间的最大值离开了。

    如果pi 有一个奇数(大于仅1)的负数,则调用最左边的负数的索引l 和最右边的负数的索引r。假设pin 元素,答案将是:

    max(
        pi[  0  ] * pi[  1  ] * ... * pi[r - 1],
        pi[l + 1] * pi[l + 2] * ... * pi[  n  ]
    )
    

    知道了这一点,很容易为这个问题的解决方案的每一步编写一个递归:在 O(n) 中将问题划分为零,另一个用于计算负数,另一个用于寻找答案。

    【讨论】:

      【解决方案4】:

      线性版本

              List<Integer> vals = new ArrayList<>(List.of(5,1,-2,1,2,3,-4,-1));
      
              int prod = 0;
              int min = 1;
              for (int v : vals) {
                  if (v == 0) {
                      // ignore zero values
                      continue;
                  }
                  if (prod == 0) {
                      prod = 1;
                  }
                  prod *= v;
                  // compute min to be the largest negative value in the list.
                  if (v < 0 && min < Math.abs(v)) {
                      min = v;
                  }
              }
              if (prod < 0) {
                  prod /= min;
              }
      
              System.out.println("Maximum product = " + prod);
          }
      
      

      递归版本

              int prod = prod(vals, new int[] {0} , vals.size());
              System.out.println("Maximum product = " + prod);
      
          public static int prod(List<Integer> vals, int[]min, int size) {
              int prod = 0;
              if(vals.size() > 0) {
                  int t = vals.get(0);
                   if (t < 0 && min[0] < Math.abs(t)) {
                          min[0] = t;
                   }
                   prod = prod(vals.subList(1,vals.size()), min, vals.size());
              }
              if (vals.isEmpty() || vals.get(0) == 0) {
                  return prod;
              }
      
              if (prod == 0) {
                 prod = 1;
              }
              prod *= t;
      
              if (vals.size() == size && prod < 0) {
                  prod/=min[0];
              }
              return prod;    
          }
      

      【讨论】:

        【解决方案5】:

        这是我的解决方案 - 将其打开以进行优化并计算运行时。这是一种通用解决方案,可在列表中找到所有整数组合的乘积。当然,有一个 O(n) 解决方案,但我也提出了这个解决方案。

        import java.util.ArrayList;
        import java.util.List;
        
        public class MaxProd {
        
            int[] input = {1, 2, 3};
        
            // int[] input = {-2, -1, 1, 2, 3};
        
            public static void main(String[] args) {
                MaxProd m = new MaxProd();
                List<Integer> ll =  m.max(0);
                for (int i : ll) {
                    System.out.println(i);
                }
                ll.sort((x,y) -> Integer.compare(x, y));
                System.out.println("The max: " + ll.get(ll.size() -1 ));
        
            }
        
            private List<Integer> max(int index) {
                if (index < input.length){
                    List<Integer> l = new ArrayList<>();
                    List<Integer> retList = max(index + 1);
        
                    for (int j : retList){
                        l.add(input[index] * j);
                    }
                    l.add(input[index]);
                    l.addAll(retList);
                    return l;
                }
                else return new ArrayList<>();
        
            }
        }
        

        打印出来:

        6
        2
        3
        1
        6
        2
        3
        The max: 6
        

        如果需求受到限制(如本例所示),则无需生成所有组合即可获得线性解决方案。另外,我在最后进行排序。注意:您可以通过在返回的列表上单次传递来轻松获得结果,以找到其他答案中指定的最大产品。

        【讨论】:

        • 这看起来使用 O(n^2log(n)) 时间(如果不排序,则为 O(n^2)),而有一个 O(n) 解决方案。
        • @PaulHankin: 不是 2^n 吗?
        • 您无需排序即可一次性获得产品。
        • @WJS:你是对的。这是处理所有组合的通用解决方案。我已经提到过了。我可以让它更清楚。
        猜你喜欢
        • 1970-01-01
        • 2016-06-14
        • 2021-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-07
        • 1970-01-01
        • 2015-05-03
        相关资源
        最近更新 更多