【问题标题】:Kadane's algorithm to find subarray with the maximum sum [duplicate]Kadane的算法找到最大和的子数组[重复]
【发布时间】:2012-03-27 13:12:38
【问题描述】:

我有以下Kadane's algorithm的实现来解决数组最大子数组的问题:

public static decimal FindBestSubsequence
    (this IEnumerable<decimal> source, out int startIndex, out int endIndex)
{
    decimal result = decimal.MinValue;
    decimal sum = 0;
    int tempStart = 0;

    List<decimal> tempList = new List<decimal>(source);

    startIndex = 0;
    endIndex = 0;

    for (int index = 0; index < tempList.Count; index++)
    {
        sum += tempList[index];
        if ((sum > result) || 
            (sum == result && (endIndex - startIndex) < (index - tempStart)))
        {
            result = sum;
            startIndex = tempStart;
            endIndex = index;
        }
        else if (sum < 0)
        {
            sum = 0;
            tempStart = index + 1;
        }
    }

    return result;
}

当我使用以-1, 2, 3 之类的负数开头的序列时,它会失败,结果是4, [0,2] 而不是5, [1,2]

为了我的一生,我找不到错误在哪里。可能是算法设计的缺陷?

提前致谢。

【问题讨论】:

    标签: c# .net algorithm kadanes-algorithm


    【解决方案1】:

    您的初始实施在主扫描周期内遭受了不必要的复杂和部分错误检查。这些检查有两个:

    • 如果找到更大的中间sum,将其存储成分作为临时结果;
    • 独立地,如果 sum 得到否定,则将其重置为 0 并准备从下一个扫描位置构建新序列。

    重构的FindBestSubsequence方法实现如下:

    public static decimal FindBestSubsequence (this IEnumerable<decimal> source, out int startIndex, out int endIndex)
    {
        decimal result = decimal.MinValue;
        decimal sum = 0;
        int tempStart = 0;
    
        List<decimal> tempList = new List<decimal>(source);
    
        startIndex = 0;
        endIndex = 0;
    
        for (int index = 0; index < tempList.Count; index++)
        {
            sum += tempList[index];
            if (sum > result)
            {
                result = sum;
                startIndex = tempStart;
                endIndex = index;
            }
            if (sum < 0)
            {
                sum = 0;
                tempStart = index + 1;
            }
        }
    
        return result;
    }
    

    现在不仅对于-1,2,3,上面的代码会产生正确的答案5,[1,2],而且它可以正确处理所有负数的数组而无需任何额外代码:输入-10,-2,-3 将返回-2,[1,1]

    【讨论】:

    • 完美。我只是在 C 中采用了一个已经存在的看似标准的实现并将其移植到 C#。你的通过了我所有的单元测试,所以我认为它是最好的选择。谢谢!
    • 另外,如果你在重构它,我会直接迭代IEnumerable,不需要创建列表的副本。并且传递多个“输出”参数通常是一种不好的做法,自定义返回类型会更好。
    • 同意列表的副本。不同意创建新的返回类型,因为在这种情况下,开始索引和结束索引的使用似乎很明显。
    【解决方案2】:

    在您的示例中,即使sum&lt;0 在循环的第一次迭代中,您始终拥有sum &gt; result,因为0 &gt; decimal.MinValue

    所以你永远不会去你的第二个案例。-

    您需要通过添加条件sum &gt; 0来更改第一个if:

    if ((sum >0 ) & ((sum > result) || 
        (sum == result && (endIndex - startIndex) < (index - tempStart))))
    {
        ...
    }
    else if (sum < 0)
    {
        ...
    }
    

    更新:

    正如我在评论中所解释的,您可以将 result 的初始化更改为 0 :

    decimal result = 0;
    

    来自维基百科:

    这个子数组要么是空的(在这种情况下它的总和为零),要么包含比在前一个位置结束的最大子数组多一个元素

    因此,如果数组只包含负数,则解是一个空子数组,总和为 0。

    【讨论】:

    • 如果我进行此更改,那么算法将失败,所有值均为负数的序列。
    • 你可以为这种情况添加一个case,返回0和一个空列表,或者如果你不想返回0,返回列表的最大值。
    • 我同意,但这意味着Kadane的算法有问题?
    • 不,我认为将结果初始化为 0 会给您提供与在第一个 if(我的回答)上添加条件相同的输出,并且它会起作用。
    • @SoMoS:是的,Ricky 是对的,未经修改的 Kadane 算法根本不适合负数,因为它每次都从零开始。
    【解决方案3】:

    改变这一行:

    decimal result = decimal.MinValue;
    

    decimal result = 0;
    

    【讨论】:

    • Thanks 使算法在所有值为负数时返回 0。输入 -1、-2、-3 时,最佳子数组是 -1。
    • @SoMoS:没错,我只是将您的代码与您发布的维基百科文章进行了比较。这也意味着他们的 python 示例也遇到了同样的问题。
    • (wikipedia) Kadane 的算法包括对数组值的扫描,在每个位置计算在该位置结束的最大子数组。这个子数组要么是空的(在这种情况下它的总和为零),要么包含比在前一个位置结束的最大子数组多一个元素。
    【解决方案4】:

    对于每个位置,您应该取其中的最大值(来自原始序列)和您编写的总和。如果原始数字更大,那么最好从“从头”开始求和,即sum = max(sum+tempList[index],tempList[index]); 然后你根本不需要 sum

    【讨论】:

      【解决方案5】:

      最后,这是我纠正算法以处理所有场景的方式,以防万一它对某人有所帮助:

          public static decimal FindBestSubsequence (this IEnumerable<decimal> source, out int startIndex, out int endIndex)
          {
              decimal result = decimal.MinValue;
              decimal sum = 0;
              int tempStart = 0;
      
              List<decimal> tempList = new List<decimal>(source);
      
              if (tempList.TrueForAll(v => v <= 0))
              {
                  result = tempList.Max();
                  startIndex = endIndex = tempList.IndexOf(result);
              }
              else
              {
                  startIndex = 0;
                  endIndex = 0;
      
                  for (int index = 0; index < tempList.Count; index++)
                  {
                      sum += tempList[index];
      
                      if (sum > 0 && sum > result || (sum == result && (endIndex - startIndex) < (index - tempStart)))
                      {
                          result = sum;
                          startIndex = tempStart;
                          endIndex = index;
                      }
                      else if (sum < 0)
                      {
                          sum = 0;
                          tempStart = index + 1;
                      }
                  }
              }
      
              return result;
          }
      

      【讨论】:

      • 感谢 Ricky Bobby 和 Groot 为我指明了正确的方向。
      • 上面的代码仍然允许一些重要的改进,例如删除所有否定的不必要的特殊情况处理数组。你可以检查我的实现重构FindBestSequence
      【解决方案6】:

      基于Gene Belitskianswer 和cmets:

          public static void Main()
          {
              var seq = new[] { -10M, -2M, -3M };
              var stuff = seq.FindBestSubsequence();
      
              Console.WriteLine(stuff.Item1 + " " + stuff.Item2 + " " + stuff.Item3);
              Console.ReadLine();
          }
      
          public static Tuple<decimal, long, long> FindBestSubsequence(this IEnumerable<decimal> source)
          {
              var result = new Tuple<decimal, long, long>(decimal.MinValue, -1L, -1L);
      
              if (source == null)
              {
                  return result;
              }
      
              var sum = 0M;
              var tempStart = 0L;
              var index = 0L;
      
              foreach (var item in source)
              {
                  sum += item;
                  if (sum > result.Item1)
                  {
                      result = new Tuple<decimal, long, long>(sum, tempStart, index);
                  }
      
                  if (sum < 0)
                  {
                      sum = 0;
                      tempStart = index + 1;
                  }
      
                  index++;
              }
      
              return result;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-14
        • 1970-01-01
        • 2019-10-13
        • 2021-05-14
        • 1970-01-01
        • 2014-07-14
        相关资源
        最近更新 更多