简体   繁体   English

在int []中找到最大和范围的最快方法

[英]Fastest way to find max sum range in int[]

Been optimizing an algorithm and came down to the last part. 一直在优化算法,并归结为最后一部分。 I have an array of integers like this: 我有一个这样的整数数组:

[ 1, 1, 2, 5, 0, 5, 3, 1, 1 ] [1,1,2,5,0,5,3,1,1]

My requirement are as follows: 我的要求如下:

  1. input: numbers of integers to sum over 输入:要求的总和数
  2. the max sum should consist of integers next to each other 最大总和应该由彼此相邻的整数组成
  3. if an integer has the value 0 the sum in the range would be invalid 如果整数的值为0,则范围中的总和将无效
  4. the max sum of integers and the index of each integer is returned 返回整数的最大值和每个整数的索引

Expected results: 预期成绩:

Given input of 2 (2 wanted) with the array as mentioned should therefor return [ 8, [ 5, 6 ] ] where 8 being the sum of integers at index 5 and 6 给定输入2(2想要)和所提到的数组应该返回[8,[5,6]],其中8是索引5和6处的整数之和

Given input of 3 (3 wanted) with the array as mentioned should therefor return [ 9, [ 5, 6, 7 ] ] where 9 being the sum of integers at index 5, 6 and 7 (notice that even though integers at indexes 3, 4, 5 have a higher sum the result is invalid due to index 4 being 0) 给定输入3(3想要)与所提到的数组应返回[9,[5,6,7]]其中9是索引5,6和7的整数之和(注意,即使索引3处的整数由于索引4为0,结果无效,4,5的总和较高

I'm currently managing this by doing a lot of looping, but was wondering if somebody had a better way of achieving this. 我现在通过做很多循环来管理这个,但是想知道是否有人有更好的方法来实现这一点。 My coding language of choice is currently C# - I would therefor appreciate if possible replies would be in C#. 我选择的编码语言目前是C# - 如果可能的回复将在C#中,我会很感激。 Any use of linq and other fancy Math features is ok as long as it's the fastest way. 任何使用linq和其他花哨的数学功能都是可以的,只要它是最快的方式。

I think you can solve this in linear time. 我想你可以在线性时间内解决这个问题。 First, replace all 0s with a very small number (-2^30 for example) so that it won't affect our sum. 首先,用非常小的数字(例如-2 ^ 30)替换所有0,这样它就不会影响我们的总和。

Then: 然后:

let s[i] = sum of first i integers
let k = number of integers required
let max = -inf    
for ( int i = k; i <= N; ++i )
  if ( s[i] - s[i - k - 1] > max )
     max = s[i] - s[i - k - 1]

You can probably avoid replacing zeros with a few extra conditions. 您可以避免使用一些额外条件替换零。 if s[i] = sum of first i integers, then s[i] - s[k - 1] gives you the sum of the integers k through i. 如果s [i] =前i个整数的和,则s [i] - s [k - 1]给出整数k到i的和。

Edit : You can do this in O(1) extra space like this: first replace all 0s. 编辑 :您可以在O(1)额外空间中执行此操作,如下所示:首先替换所有0。

then: 然后:

max = cr = sum of first k integers.
for ( int i = k + 1; i <= N; ++i )
{
  cr = cr + numbers[i] - numbers[i - k] 
  if ( cr > max )
    max = cr; // also update positions
}

To avoid replacing zeroes in the first solution, just skip k spaces ahead when encountering a zero. 为避免在第一个解决方案中替换零,只需在遇到零时跳过k空格。 In the second solution, skip k or k + 1 (depends on how you choose to implement this special case) spaces ahead, but be sure to rebuild your cr variable when doing the skip! 在第二个解决方案中,跳过k或k + 1(取决于你如何选择实现这种特殊情况)前面的空格,但一定要在跳过时重建你的cr变量!

The below code should do the trick. 下面的代码应该可以解决问题。 The performance will depend on the size of the range to be sum'ed. 性能将取决于要求和的范围的大小。 The more elemnts the better it will perform compared to the naïve of adding a subset in every iteration 与在每次迭代中添加子集的天真相比,越多的元素表现越好

 int getSum(int[] arr, int wanted)
        {
            var pre = new int[arr.Length];
            pre[0] = 0;
            pre[1] = arr[0];
            int max = 0;
            int skip = 1;
            for (var i = 1; i < arr.Length; i++)
            {
                skip--;
                //if the sum is marked invalid with a zero skip
                var current = arr[i];
                //calculate the index once
                int preIndex = i + 1;
                if (current == 0)
                {
                    skip = wanted;
                    pre[preIndex] = pre[i];
                    continue;
                }
                //store the sum of all elements until the current position
                pre[preIndex] = pre[i] + current;
                //find the lower end of the range under investigation now
                var lower = i - wanted;
                //if we haven't reached the wanted index yet we have no sum or if 
                //it's less than wanted element since we met a 0 
                //just go to the next element
                if (lower < 0 || skip > 0)
                    continue;
                var sum = pre[preIndex] - pre[lower];
                if (sum > max)
                    max = sum;
            }
            return max;
        }

Is "easy to read" code considered an "optimization"? “易读”代码被认为是“优化”吗?

int numberOfElements = 4;   //parameter
int[] numbers = new[] { 1, 1, 2, 5, 0, 5, 3, 1, 1 };    //numbers


int[] result =
     //cicle each element
    (from n in Enumerable.Range(0, numbers.Length - numberOfElements + 1)
     //keep the (n + numberOfElements) elements
     let myNumbers = from p in Enumerable.Range(n, numberOfElements)
                     select numbers[p]
     //keep the sum (if we got a 0, sum is 0)
     let sum = myNumbers.Contains(0) ? 0 : myNumbers.Sum()
     orderby sum descending     //order by sum
     select myNumbers)          //select the list
        .First().ToArray();     //first is the highest

Consider adding a .AsParallel() for performance when .NET 4 is out. 当.NET 4用完时,考虑为性能添加.AsParallel()

This is O(n) time and O(1) space and goes only once through the array. 这是O(n)时间和O(1)空间,并且只通过数组一次。

static public int[] FindMaxSumRange(int[] data, int n)
{
    // two pointers showing the lower and upper bound of current running sum
    int upper = 0, lower = 0;
    // best result found yet
    int max_found = 0;
    int max_position = -1;

    while (lower <= data.Length - n) {
        int running_sum = 0;
        // prefill running sum with n items
        for (upper = lower; upper - lower < n && upper < data.Length; upper++) {
            if (data[upper] == 0) {
                // found a zero, set lower pointer to the next item
                lower = upper + 1;
                break;
            }
            running_sum += data[upper];
        }
        if (upper - lower != n) {
            // found a zero, restart at new lower position
            continue;
        }
        if (running_sum > max_found) {
            max_found = running_sum;
            max_position = lower;
        }
        while (upper < data.Length) {
            if (data[upper] == 0) {
                // found a zero, set lower pointer to the next item
                lower = upper;
                break;
            }
            running_sum += data[upper] - data[lower];
            if (running_sum > max_found) {
                max_found = running_sum;
                max_position = lower;
            }
            upper++; lower++;
        }
        lower++;
    }
    return new int[]{max_found, max_position};
}

This return the max sum and the position it was found at. 这将返回最大总和及其找到的位置。 If you need to get a list of the indices, just build the range [max_position, max_position + n) 如果需要获取索引列表,只需构建范围[max_position,max_position + n)

The following was a rant, and totally untested. 以下是一个咆哮,完全没有经过考验。 Not even sure if it will compile. 甚至不确定它是否会编译。 I leave it to others to improve it. 我把它留给别人改进它。

using System;
using System.Linq;
public int[] max(int[] a, int amount) {
    var max = int.MinValue;
    var maxPos = 0;
    if (a.length < amount) return null;
    var c = 0;
    while (c == 0) {
        for (int i = 0; i < amount; i++) {
            if (a[i + maxPos] == 0) {
                c = 0;
                break; // try again
            }
            c += a[i];
        }
        if (c != 0) maxPos = i - amount;
    }
    if (c == 0) return null;
    max = c;
    for (int i = maxPos; i + amount < a.length; i++) {
        if(a[i] == 0) {
            i += amount - 1;
            continue;
        }
        c -= a[i];
        c += a[i + amount];
        if (c > max) {
            c = max;
            maxPos = i;
        }
    }
    if (c == 0) return null;
    var result = new int[amount + 1];
    result[0] = max;
    for (int i = 0; i < amount; i++)
        result[i + 1] = maxPos + i;
    return result;
}

Idea is 1. Split array to groups to measure sum 2. Count sum for every group 3. Figure out max sum 想法是1.将数组拆分为组以测量总和2.计算每组的总和3.计算最大总和

Here is the code 这是代码

private Result GetMax(ICollection<int> items, int itemCount)
{
  return items.
    Take(items.Count - (itemCount - 1)).
    Select((value, index) => items.Skip(index).Take(itemCount)).
    Select((group, index) =>
      new Result
      {
        Index = index,
        Sum = group.Aggregate(0, (sum, i) => sum + (i == 0 ? int.MinValue : i))
      }).
    Max();
}

private struct Result : IComparable<Result>
{
  public int Index { get; set; }
  public int Sum { get; set; }

  public int CompareTo(Result other)
  {
    return Sum.CompareTo(other.Sum);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM