【发布时间】:2011-07-28 21:22:09
【问题描述】:
一个数组既包含正元素又包含负元素,求和等于0的最大子数组。
【问题讨论】:
-
空子数组的和为零。 :)
-
寻找最大子数组不是常见的问题吗?
标签: algorithm
一个数组既包含正元素又包含负元素,求和等于0的最大子数组。
【问题讨论】:
标签: algorithm
当前接受的答案中的链接需要注册成为会员,我没有它的内容。
此算法将找到总和为 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;可以通过不同的方式实现实现,包括使用带有对的 HashMap,但请注意上面“注意”部分中的特殊情况。
例子:
int[] input = {4, 6, 3, -9, -5, 1, 3, 0, 2}
int[] tmp = {4, 10, 13, 4, -1, 0, 3, 3, 5}
****更新****
假设在我们的 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)。
【讨论】:
这与 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 }
【讨论】:
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)
【讨论】:
这是我的实现,这是一种显而易见的方法,因此它可能没有经过优化,但至少它很清晰。如果我错了,请纠正我。
从数组的每个索引开始,计算各个总和(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]
【讨论】:
以下解决方案在不使用动态编程但使用简单递归的情况下找到具有给定总和 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)
【讨论】:
这是java 中的O(n) 实现
这个想法是遍历给定的数组和每个元素arr[i],计算从0到i的元素总和,将每个sum存储在HashMap中。
如果元素为0,则将其视为ZeroSum子数组。
如果sum变成0,则有一个ZeroSum子数组,从0到i。
如果之前在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
【讨论】:
数组包含正数和负数。找到总和最大的子数组
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;
}
【讨论】:
下面的代码可以找出每一个总和为给定数字的可能子数组,并且(当然)它可以找出这种最短和最长的子数组。
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 + "]");
}
【讨论】:
希望对您有所帮助。
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);
}
}
}
【讨论】:
解决办法之一:
假设我们有一个整数数组, 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 索引并返回或中断循环。
因此,它在线性时间内完成。同样,我们也不需要使用任何其他数据结构。
【讨论】:
此问题的另一种解决方案可能是: 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]));
}
【讨论】:
希望这会有所帮助。
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);
}
}
}
【讨论】: