和等于 0 的最大子数组

Posted

技术标签:

【中文标题】和等于 0 的最大子数组【英文标题】:maximum subarray whose sum equals 0 【发布时间】:2011-07-28 21:22:09 【问题描述】:

一个数组既包含正元素又包含负元素,求和等于0的最大子数组。

【问题讨论】:

空子数组的和为零。 :) 寻找最大子数组不是常见的问题吗? 【参考方案1】:

当前接受的答案中的链接需要注册成为会员,我没有它的内容。

此算法将找到总和为 0 的所有子数组,并且可以轻松修改它以找到最小的子数组或跟踪开始和结束索引。这个算法是O(n)

给定一个int[] input 数组,您可以创建一个int[] tmp 数组,其中tmp[i] = tmp[i - 1] + input[i]; tmp 的每个元素将存储输入到该元素的总和(数组的前缀总和)。

现在,如果您检查 tmp,您会注意到可能存在彼此相等的值。假设这个值在索引j an k with j < k 处,那么直到j 的输入总和等于直到k 的总和,这意味着数组在j 和@987654328 之间的部分的总和@是0!具体来说,0 sum 子数组将从索引 j + 1 到 k。

注意:如果是j + 1 == k,那么k is 0 就是这样! ;) 注意:算法应考虑虚拟tmp[-1] = 0; 注意:空数组的总和为 0,它是最小的,这种特殊情况也应该在面试中提出。然后面试官会说那不算数,但那是另一个问题! ;)

可以通过不同的方式实现实现,包括使用带有对的 HashMap,但请注意上面“注意”部分中的特殊情况。

例子:

int[] input = 4,  6,  3, -9, -5, 1, 3, 0, 2
int[] tmp =   4, 10, 13,  4, -1, 0, 3, 3, 5
tmp 中索引 0 和 3 处的值 4 ==> 和 tmp 1 到 3 = 0,长度 (3 - 1) + 1 = 3 索引 5 处 tmp 中的值 0 ==> 和 tmp 0 到 5 = 0,长度 (5 - 0) + 1 = 6 tmp 中索引 6 和 7 处的值 3 ==> 和 tmp 7 到 7 = 0,长度 (7 - 7) + 1 = 1

****更新****

假设在我们的 tmp 数组中,我们最终有多个具有相同值的元素,那么您必须考虑其中的每一对相同的元素!示例(记住索引“-1”处的虚拟“0”):

int[] array = 0, 1, -1, 0
int[] tmp = 0, 1, 0, 0

通过应用上述相同的算法,0-sum 子数组由以下索引(包括)分隔:

[0] [0-2] [0-3] [1-2] [1-3] [3]

尽管存在多个具有相同值的条目可能会影响算法的复杂性,具体取决于实现,我相信通过在 tmp 上使用倒排索引(将值映射到它出现的索引),我们可以保持运行时间为 O(n)。

【讨论】:

>> 考虑这个测试用例: 0, 1, -1, 0 临时数组是 0, 1, 0, 0 所以,根据你的说法,有多少子数组的总和是零? 感谢您指出这个特殊情况,请参阅我对上述答案的更新。应该有 6 个(如果需要,可以加上空数组)! 为什么这个答案不被接受?我可以看到它工作得很好。 您能否详细说明当存在大量重复条目时如何保持 O(n) 运行时间?谢谢 @shreyasva 你应该接受这个答案。你发现它有什么问题吗?【参考方案2】:

这与 Gevorg 建议的行相同,但我使用哈希映射进行快速查找。 O(n) 复杂度虽然使用了额外的空间。

private static void subArraySumsZero()

    int [] seed = new int[] 1,2,3,4,-9,6,7,-8,1,9;
    int currSum = 0;
    HashMap<Integer, Integer> sumMap = new HashMap<Integer, Integer>();
    for(int i = 0 ; i < seed.length ; i ++)
    
        currSum += seed[i];
        if(currSum == 0)
        
            System.out.println("subset :  0 - " + i + " ");
        
        else if(sumMap.get(currSum) != null)
        
            System.out.println("subset :  " 
                                + (sumMap.get(currSum) + 1) 
                                + " - " + i + " ");
            sumMap.put(currSum, i);
        
        else
            sumMap.put(currSum, i);
    
    System.out.println("HASH MAP HAS: " + sumMap);

生成的输出具有元素索引(从零开始):

subset :  1 - 4 
subset :  3 - 7 
subset :  6 - 8 

【讨论】:

您的实现有问题。对于数组 [4,10,-6,-4,0,2,3,-5,1,0,2] 您的输出将是 - 1 3 45 7 9 实际答案应该是 1 345 791 41 5 1 7 4 7 @DJ': 1, 5 不应该出现在答案中。【参考方案3】:
1. Given A[i]
  A[i] | 2 |  1 | -1 | 0 | 2 | -1 | -1
-------+---|----|--------|---|----|---
sum[i] | 2 |  3 |  2 | 2 | 4 |  3 |  2

2. sum[i] = A[0] + A[1] + ...+ A[i]
3. build a map<Integer, Set>
4. loop through array sum, and lookup map to get the set and generate set, and push <sum[i], i> into map.

Complexity O(n)

【讨论】:

这只有在相同的数字是连续的情况下才有效【参考方案4】:

这是我的实现,这是一种显而易见的方法,因此它可能没有经过优化,但至少它很清晰。如果我错了,请纠正我。

从数组的每个索引开始,计算各个总和(tempsum)并将其与所需总和(在本例中为 sum = 0)进行比较。由于整数是有符号的,我们必须计算所有可能的组合。

如果您不需要子数组的完整列表,您可以随时将条件放在内部循环中以跳出它。 (假设你只是想知道这样的子数组是否存在,只要 tempsum = sum 时返回 true)。

public static string[] SubArraySumList(int[] array, int sum)

    int tempsum;
    List<string> list = new List<string>();
    for (int i = 0; i < array.Length; i++)
    
        tempsum = 0;
        for (int j = i; j < array.Length; j++)
        
            tempsum += array[j];
            if (tempsum == sum)
            
                list.Add(String.Format("[0-1]", i, j));
            
        
    
    return list.ToArray();

调用函数:

int[] array = SubArraySumList(new int  0, -1, 1, 0 , 0));

打印输出数组的内容:

[0-0], [0-2], [0-3], [1-2], [1-3], [3-3]

【讨论】:

【参考方案5】:

以下解决方案在不使用动态编程但使用简单递归的情况下找到具有给定总和 k 的最大长度子数组。这里 i_s 是起始索引,i_e 是 sum 当前值的结束索引

##Input the array and sum to be found(0 in your case)
a = map(int,raw_input().split())
k = int(raw_input())

##initialize total sum=0
totalsum=0

##Recursive function to find max len 0
def findMaxLen(sumL,i_s,i_e):
    if i_s<len(a)-1 and i_e>0: 
        if sumL==k:
            print i_s, i_e
            return (i_s,i_e)
        else:
            x = findMaxLen(sumL-a[i_s],i_s+1,i_e)
            y = findMaxLen(sumL-a[i_e],i_s,i_e-1)
            if x[1]-x[0]>y[1]-y[0]:
                return x
            else:
                return y
    else:
        ##Result not there
        return (-1,-1)


## find total sum
for i in range(len(a)):
    totalsum += a[i]

##if totalsum==0, max array is array itself
if totalsum == k:
    print "seq found at",0,len(a)-1

##else use recursion
else:
    print findMaxLen(totalsum,0,len(a)-1)

由于递归内存堆栈,时间复杂度为 O(n),空间复杂度为 O(n)

【讨论】:

【参考方案6】:

这是java 中的O(n) 实现

这个想法是遍历给定的数组和每个元素arr[i],计算从0i的元素总和,将每个sum存储在HashMap中。

如果元素为0,则将其视为ZeroSum子数组。

如果sum变成0,则有一个ZeroSum子数组,从0i

如果之前在HashMap 中看到过当前总和,则存在一个ZeroSum 子数组,从该点到i

代码:

import java.util.*;
import java.lang.*;

class Rextester
  
    private static final int[] EMPTY = ;
    // Returns int[] if arr[] has a subarray with sero sum
    static int[] findZeroSumSubarray(int arr[])
    
        if (arr.length == 0) return EMPTY;
        // Creates an empty hashMap hM
        HashMap<Integer, Integer> hM = new HashMap<Integer, Integer>();
        // Initialize sum of elements
        int sum = 0;        
        for (int i = 0; i < arr.length; i++)
           
            sum += arr[i]; 
            if (arr[i] == 0)   //Current element is 0
            
               return new int[]0; 
            
            else if (sum == 0)  // sum of elements from 0 to i is 0
            
                return Arrays.copyOfRange(arr, 0, i+1);
            
            else if (hM.get(sum) != null) // sum is already present in hash map
            
                return Arrays.copyOfRange(arr, hM.get(sum)+1, i+1);
            
            else
            
                // Add sum to hash map
                hM.put(sum, i);
            
            
        // We reach here only when there is no subarray with 0 sum
        return null;
            

    public static void main(String arg[])
    
        //int arr[] = ;
        int arr[] =  2, -3, 1, 4, 6; //Case left
        //int arr[] =  0, 2, -3, 1, 4, 6; //Case 0
        //int arr[] =  4, 2, -3, 1, 4; // Case middle
        int result[] = findZeroSumSubarray(arr);
        if (result == EMPTY)
            System.out.println("An empty array is ZeroSum, LOL"); 
        
        else if ( result != null)
            System.out.println("Found a subarray with 0 sum :" );
            for (int i: result) System.out.println(i);
        
        else
            System.out.println("No Subarray with 0 sum");            
               

请看这里的实验:http://rextester.com/PAKT41271

【讨论】:

【参考方案7】:

数组包含正数和负数。找到总和最大的子数组

public static int findMaxSubArray(int[] array)

    int max=0,cumulativeSum=0,i=0,start=0,end=0,savepoint=0;
    while(i<array.length)
    
        if(cumulativeSum+array[i]<0)
        
            cumulativeSum=0;
            savepoint=start;
            start=i+1;
        
        else
            cumulativeSum=cumulativeSum+array[i];
        if(cumulativeSum>max)
        
                max=cumulativeSum;
                savepoint=start;
                end=i;
        
        i++;
    

    System.out.println("Max : "+max+"  Start indices : "+savepoint+"  end indices : "+end);
    return max;


【讨论】:

【参考方案8】:

下面的代码可以找出每一个总和为给定数字的可能子数组,并且(当然)它可以找出这种最短和最长的子数组。

public static void findGivenSumSubarray(int arr[], int givenSum) 
    int sum = 0;
    int sStart = 0, sEnd = Integer.MAX_VALUE - 1;  // Start & end position of the shortest sub-array
    int lStart = Integer.MAX_VALUE - 1, lEnd = 0;  // Start & end position of the longest  sub-array

    HashMap<Integer, ArrayList<Integer>> sums = new HashMap<>();
    ArrayList<Integer> indices = new ArrayList<>();

    indices.add(-1);
    sums.put(0, indices);

    for (int i = 0; i < arr.length; i++) 
        sum += arr[i];
        indices = sums.get(sum - givenSum);
        if(indices != null) 
            for(int index : indices) 
                System.out.println("From #" + (index + 1) + " to #" + i);
            
            if(i - indices.get(indices.size() - 1) < (sEnd - sStart + 1)) 
                sStart = indices.get(indices.size() - 1) + 1;
                sEnd = i;
            
            if(i - indices.get(0) > (lEnd - lStart + 1)) 
                lStart = indices.get(0) + 1;
                lEnd = i;
            
        
        indices = sums.get(sum);
        if(indices == null) 
            indices = new ArrayList<>();
        
        indices.add(i);
        sums.put(sum, indices);
    

    System.out.println("Shortest sub-arry: Length = " + (sEnd - sStart + 1) + ", [" + sStart + " - " + sEnd + "]");
    System.out.println("Longest  sub-arry: Length = " + (lEnd - lStart + 1) + ", [" + lStart + " - " + lEnd + "]");

【讨论】:

【参考方案9】:

希望对您有所帮助。

 private static void subArrayZeroSum(int array[] , int findSum)
    Map<Integer,HashSet<Integer>> map = new HashMap<Integer,HashSet<Integer>>();
    int sum = 0;
   for(int index = 0 ; index < array.length ; index ++)
       sum +=array[index];
       if(array[index] == findSum)
           System.out.println(" ["+index+"]");
       
       if(sum == findSum && index > 0)
           System.out.println(" [ 0 , "+index+" ]");
       
       if(map.containsKey(sum))
           HashSet<Integer> set = map.get(sum);
           if(set == null)
               set = new HashSet<Integer>();

           set.add(index);
           map.put(sum, set);

           for(int val : set)

               if(val + 1 != index && (val + 1) < index)
                  System.out.println("["+(val + 1) +","+index+" ]");
               
           

        else
            HashSet<Integer> set = map.get(sum);
               if(set == null)
                   set = new HashSet<Integer>();
               set.add(index);
            map.put(sum, set);
        
           

【讨论】:

【参考方案10】:

解决办法之一:

假设我们有一个整数数组, int[] arr = 2,1,-1,-2;

我们将使用 for 循环遍历,直到找到数字

使用内部循环,我们将遍历赋值给 j = i-1 所以,我们可以找到正值。

for(int i = 0; i<arr.length; i++)
      int j = 0;
      int sum = arr[i];
      if(arr[i] < 0)
      j = i - 1;

我们将有一个 sum 变量,它维护 arr[i] 和 arr[j] 的总和并更新结果。

如果和

for(j = i-1; j>=0; j--) 
      sum = sum + arr[j];
      if(sum == 0)
      System.out.println("Index from j=" + j+ " to i=" + i);
      return true;
   

如果总和> 0,则我们必须移动数组的右侧,因此,我们将增加 i

当我们找到 sum == 0 时,我们可以打印 j 和 i 索引并返回或中断循环。

因此,它在线性时间内完成。同样,我们也不需要使用任何其他数据结构。

【讨论】:

【参考方案11】:

此问题的另一种解决方案可能是: 1. 计算整个数组的总和 2. 现在按照下面的公式得到和为零的最大子数组:

Math.max(find(a,l+1,r,sum-a[l]), find(a,l,r-1,sum-a[r]));
where l=left index, r= right index, initially their value=0 and a.length-1

想法很简单,我们可以通过 sum=0 获得最大大小,是数组的大小,然后我们开始递归地从左右跳过元素,当我们得到 sum=0 的那一刻我们停止。以下是相同的代码:

static int find(int a[]) 
    int sum =0;
    for (int i = 0; i < a.length; i++) 
        sum = sum+a[i];
    

    return find(a, 0, a.length-1, sum);



static int find(int a[], int l, int r, int sum) 
    if(l==r && sum>0) 
        return 0;
    
    if(sum==0) 
        return r-l+1;
    
    return Math.max(find(a,l+1,r,sum-a[l]), find(a,l,r-1,sum-a[r]));


【讨论】:

【参考方案12】:

希望这会有所帮助。

int v[DIM] = 2, -3,  1,  2,  3,  1,  4, -6,  7, -5, -1;
int i,j,sum=0,counter=0;

    for (i=0; i<DIM; i++) 
        sum = v[i];
        counter=0;
        for (j=i+1; j<DIM;j++) 
            sum += v[j];
            counter++;
            if (sum == 0) 
                printf("Sub-array starting from index %d, length %d.\n",(j-counter),counter +1);
            
        
    

【讨论】:

以上是关于和等于 0 的最大子数组的主要内容,如果未能解决你的问题,请参考以下文章

最大子数组的和

找到最大和连续子数组,使得子数组的长度小于等于 k?

返回一个整数数组中最大子数组的和

返回一个整数数组中最大子数组的和

返回一个整数数组中最大子数组的和

java 最大尺寸子阵列和等于k(和为k的最长连续子数组)