【问题标题】:Fastest way to find max sum range in int[]在 int[] 中找到最大和范围的最快方法
【发布时间】:2010-02-17 17:53:52
【问题描述】:

一直在优化一个算法,到了最后一部分。我有一个这样的整数数组:

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

我的要求如下:

  1. 输入:要求和的整数个数
  2. 最大和应由彼此相邻的整数组成
  3. 如果整数的值为 0,则该范围内的总和无效
  4. 返回整数的最大总和和每个整数的索引

预期结果:

给定输入 2(需要 2 个)和所提到的数组,因此应该返回 [ 8, [ 5, 6 ] ] 其中 8 是索引 5 和 6 处的整数之和

给定输入 3(需要 3 个)和所提到的数组,因此应该返回 [ 9, [ 5, 6, 7 ] ] 其中 9 是索引 5、6 和 7 处的整数之和(请注意,即使索引 3、4、5 处的整数具有更高的和,由于索引 4,结果是无效的为 0)

我目前正在通过大量循环来管理此问题,但想知道是否有人有更好的方法来实现这一点。我目前选择的编码语言是 C# - 因此,如果可能的回复是 C#,我将不胜感激。只要是最快的方式,任何使用 linq 和其他花哨的数学功能都可以。

【问题讨论】:

  • 对不起,我发布了一个答案。至少它不在 C# 中。
  • 不,这绝对不是家庭作业……我希望是
  • 然后你可以删除其他人添加的那些标签,甚至不验证。

标签: c# algorithm


【解决方案1】:

我认为你可以在线性时间内解决这个问题。首先,将所有的 0 替换为一个非常小的数字(例如 -2^30),这样它就不会影响我们的总和。

然后:

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]

您可能可以避免用一些额外的条件替换零。如果 s[i] = 第一个 i 整数的总和,则 s[i] - s[k - 1] 为您提供 k 到 i 的整数之和。

编辑: 您可以像这样在 O(1) 额外空间中执行此操作:首先替换所有 0。

然后:

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
}

为避免在第一个解决方案中替换零,只需在遇到零时向前跳过 k 个空格。在第二个解决方案中,跳过 k 或 k + 1 (取决于您如何选择实现这种特殊情况)空格,但请务必在执行跳过时重建您的 cr 变量!

【讨论】:

  • +1 用于跳过的第二个解决方案。需要根据遇到的数字检查用非常小的数字替换零;跳过是一般的解决方案,也不是很难。
【解决方案2】:

下面的代码应该可以解决问题。性能将取决于要求和的范围的大小。与在每次迭代中添加子集的天真相比,元素越多性能越好

 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;
        }

【讨论】:

    【解决方案3】:

    “易于阅读”的代码是否被视为“优化”?

    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
    

    考虑添加 .AsParallel() 以在 .NET 4 推出时提高性能。

    【讨论】:

    • 感谢您使用 linq 的回复。 Linq 功能强大,但我正在寻找执行速度。在我不必担心速度的普通解决方案中,我会寻找这样的答案。您的代码在数组中返回正确的数字,但不是 numberIndexes 也不是总和(仅由它排序)。但是,修改它以返回它并不难:)
    【解决方案4】:

    这是 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};
    }
    

    这将返回最大总和和找到它的位置。如果您需要获取索引列表,只需构建范围 [max_position, max_position + n)

    【讨论】:

    • 您当前的答案导致了无限循环
    • 啊,除非你再次测试,否则什么都不起作用的规则再次出现。终止条件有轻微错误。现已更正,谢谢。
    【解决方案5】:

    以下内容是胡言乱语,完全未经测试。甚至不确定它是否会编译。我把它留给其他人来改进它。

    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;
    }
    

    【讨论】:

      【解决方案6】:

      想法是 1. 将数组拆分为组以测量总和 2.计算每组的总和 3. 计算最大总和

      这里是代码

      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);
        }
      }
      

      【讨论】:

      • 算法速度快,易读不知道为什么这个linq算法比其他提交的快很多
      猜你喜欢
      • 1970-01-01
      • 2021-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-08
      • 2017-09-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多