使用递归找到最大产品

Posted

技术标签:

【中文标题】使用递归找到最大产品【英文标题】: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循环也可以? 我希望使用递归,只是因为我想通过递归来提高我的技能,这一直是我的弱点。 【参考方案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:你是对的。这是处理所有组合的通用解决方案。我已经提到过了。我可以让它更清楚。

以上是关于使用递归找到最大产品的主要内容,如果未能解决你的问题,请参考以下文章

如何在sql中找到客户明智类别中的最大产品ID?

猫鼬递归嵌套

office2007 激活时说产品密钥的激活次数已达到microsoft软件许可款的最大软件许可允许值

Microsoft office2016版产品密钥的激活次数达到了最大允许次数'这应该怎么解决啊

在c中使用递归找到最大的素数

找到最大递归深度