找到一个子数组,其总和可以用数字K整除,子数组应该是所有可能子数组的最大总和

时间:2013-08-01 05:15:32

标签: algorithm

我一直在练习算法问题而且我遇到了这个问题 给定一个数组(+ ve和-ve)数字,我必须找到一个连续的子数组,使得该和可以被任何数字K整除,并且子数组应该是可能的最大总和。例如。
a={1,2,2,1,1,4,5,3}k=5以及可被k整除的最大和子数组将为
{2,2,1,1,4,5}, sum = 15
目前我能想到的是,每个元素都有两种可能性,要么将它包含在目标子阵列中,要么不包含在目标子阵列中。但这将是一个指数算法 编辑:是否有可能在线性时间解决这个问题。请帮忙

4 个答案:

答案 0 :(得分:3)

此问题的关键字是前缀sum。

计算它们的伪代码如下所示:

int prefix_sum[N];
prefix_sum[0] = array[0];
for (i = 1; i < n; i++)
    prefix_sum[i] = prefix_sum[i-1] + array[i];

现在我们有前缀sum,唯一剩下的就是找到子数组。 我们可以通过从最后一个减去子阵列的(前一个)第一个前缀和值来查看子阵列的总和。

我们关心的属性是和和K的可分性。为了找到最大和子数,我们一次查看每个元素。虽然我们一次查看每个元素,但我们做了4件事:

  1. 除以前缀sum模数K:rem[i] = prefix_sum[i] % K;。这样,当且仅当rem[start_subarray] + rem[end_subarray] == K时,我们才知道子数组是有效的。 但是我们不仅用它来检查子数组是否可以被整除,我们也不能用它来查找子数组(见下文)。

  2. 我们使用大小为max_start的数组K。当我们计算prefix_sum[i]的余数时,当prefix_sum [i]大于i中当前索引的prefix_sum时,我们将max_start[rem[i]]存储在max_start[rem[i]]中。 现在我们能够在O(1)中查找具有最大前缀和的索引,该索引具有给定的余数。

  3. 对于我们的元素array[i],我们查看rem[i]并查找具有余数K-rem[i]的最大prefix_sum的元素。当我们这样做时,我们得到a)可被K整除的子数组,b)具有最大的总和(对于以该元素array[i]结尾的所有数组)。

  4. 我们检查这个数组的总和是否大于我们当前最大的找到的数组,以及何时将此数组设置为我们的新最佳射手。

  5. 细节很毛茸茸,因为你必须寻找正确的索引,你必须关心所有异常情况(比如没有找到任何东西......),但我想你会得到算法的想法。运行时间为O(n),由于前缀总和,它应该适用于负数和正数。

答案 1 :(得分:1)

如果不是负数,则每个具有可被K整除的和的连续子阵列应该由最多K个元素的较小的可累加子阵列组成。但是对于负数而言并非如此。

所以基本上唯一的选择是检查每个子数组的总和是否可以整除。像这样:

a = [1,2,2,1,1,4,5,3]
K = 5

max_a = []
max_len = 0

for i in range(len(a)):
    for j in range(i+1, len(a)+1):
        s = sum(a[i:j])
        if s % K == 0 and j-i > max_len:    
            max_len = j-i
            max_a = a[i:j]

print max_a

嗯,这是多项式,但仍然不是很有效。

答案 2 :(得分:0)

我为此写了一个分而治之的算法。

如果FindMaxSubarrayDivisible(array,start,end,maxStart,maxEnd,sum,k)是用于计算可被k整除的最大连续子数组的函数,则:

FindMaxSubarrayDivisible(array, start, end, out maxStart, out maxEnd, out sum, k)
    mid=(start+end)/2;
    FindMaxSubarrayDivisible(array, start, mid, out leftMaxStart, out leftMaxEnd, out leftSum, k)
    FindMaxSubarrayDivisible(array, mid, end, out rightMaxStart, out rightMaxEnd, out rightSum, k)
    FindMaxCrossingSubarrayDivisible(array, start, end, out crossMaxStart, out crossMaxEnd, out crossSum, k)
    Determine the max of the three above, if exists

FindMaxCrossingSubarrayDivisible可以使用O(k)存储在O(max(n,k))时间内完成。我的想法是具有k个整数的数组,其中每个元素存储余数i的数组的右侧的最​​大交叉和,其中0 <= i <1。 ķ。对阵列的左侧执行相同操作,然后在O(k)时间内合并。如果k <&lt; n,那么这个算法是O(n lg n)时间。

我为此编写了以下C#代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    class Program
    {
        static int k;

        static void Main(string[] args)
        {
            k = 5;
            int maxStart;
            int maxEnd;
            int sum;

            int[] a = new int[] { };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { 1 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { 2,1 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { 2,3 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { 3,2,3,2 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { -5,10,15,-5 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { 1, 2, 2, 1, 1, 4, 5, 3 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);

            a = new int[] { -1,-2,-3,-4,-5 };
            f(a, 0, a.Length, out maxStart, out maxEnd, out sum);
            Console.WriteLine("{0},{1},{2}", maxStart, maxEnd, sum);
        }

        static void f(int[] a, int start, int end, out int maxStart, out int maxEnd, out int sum)
        {
            if (end - start < 0)
            {
                throw new ArgumentException();
            }
            else if (end - start == 0)
            {
                maxStart = start;
                maxEnd = end;
                sum = 0;
            }
            else if (end - start == 1)
            {
                if (a[start] % k == 0)
                {
                    maxStart = start;
                    maxEnd = end;
                    sum = a[start];
                }
                else
                {
                    maxStart = -1;
                    maxEnd = -1;
                    sum = 0;
                }
            }
            else
            {
                int leftMaxStart;
                int leftMaxEnd;
                int leftMaxSum;
                int rightMaxStart;
                int rightMaxEnd;
                int rightMaxSum;
                int mid = (start + end) / 2;
                f(a, start, mid, out leftMaxStart, out leftMaxEnd, out leftMaxSum);
                f(a, mid, end, out rightMaxStart, out rightMaxEnd, out rightMaxSum);

                int[] r = new int[k];
                int[] rightEnds = new int[k];   //right end index array
                for (int i = 0; i < k; ++i)
                {
                    rightEnds[i] = -1;
                }
                int midSum = a[mid - 1] + a[mid];
                int midRightSum = midSum;
                int mod = Math.Abs(midRightSum % k);
                if (midRightSum > r[mod] || rightEnds[mod] == -1)
                {
                    r[mod] = midRightSum;
                    rightEnds[mod] = mid + 1;
                }
                for (int i = mid + 1; i < end; ++i)
                {
                    midRightSum += a[i];
                    mod = Math.Abs(midRightSum % k);
                    if (midRightSum > r[mod] || rightEnds[mod] == -1)
                    {
                        r[mod] = midRightSum;
                        rightEnds[mod] = i + 1;
                    }
                }

                int[] l = new int[k];
                int[] leftStarts = new int[k];  //left end index array
                for (int i = 0; i < k; ++i)
                {
                    leftStarts[i] = -1;
                }
                int leftSum = 0;
                for (int i = mid - 2; i >= start; --i)
                {
                    leftSum += a[i];
                    mod = Math.Abs(leftSum % k);
                    if (leftSum > l[mod] || leftStarts[mod] == -1)
                    {
                        l[mod] = leftSum;
                        leftStarts[mod] = i;
                    }
                }

                int crossMaxSum = int.MinValue;
                int crossMaxStart = -1;
                int crossMaxEnd = -1;
                if (rightEnds[0] != -1)
                {
                    crossMaxSum = r[0];
                    crossMaxStart = mid - 1;
                    crossMaxEnd = rightEnds[0];
                    if (leftStarts[0] != -1)
                    {
                        int crossSum = l[0] + r[0];
                        if (crossSum > crossMaxSum)
                        {
                            crossMaxSum = crossSum;
                            crossMaxStart = leftStarts[0];
                            crossMaxEnd = rightEnds[0];
                        }
                    }
                }
                for (int i = 1; i < k; ++i)
                {
                    int crossSum = l[i] + r[k-i];
                    if (crossSum > crossMaxSum)
                    {
                        crossMaxSum = crossSum;
                        crossMaxStart = leftStarts[i];
                        crossMaxEnd = rightEnds[k-i];
                    }
                }

                if (crossMaxStart != -1)
                {
                    if (leftMaxStart != -1)
                    {
                        if (rightMaxStart != -1)
                        {
                            if (leftMaxSum >= rightMaxSum && leftMaxSum >= crossMaxSum)
                            {
                                maxStart = leftMaxStart;
                                maxEnd = leftMaxEnd;
                                sum = leftMaxSum;
                            }
                            else if (crossMaxSum >= leftMaxSum && crossMaxSum >= rightMaxSum)
                            {
                                maxStart = crossMaxStart;
                                maxEnd = crossMaxEnd;
                                sum = crossMaxSum;
                            }
                            else
                            {
                                maxStart = rightMaxStart;
                                maxEnd = rightMaxEnd;
                                sum = rightMaxSum;
                            }
                        }
                        else
                        {
                            if (leftMaxSum >= crossMaxSum)
                            {
                                maxStart = leftMaxStart;
                                maxEnd = leftMaxEnd;
                                sum = leftMaxSum;
                            }
                            else
                            {
                                maxStart = crossMaxStart;
                                maxEnd = crossMaxEnd;
                                sum = crossMaxSum;
                            }
                        }
                    }
                    else
                    {
                        if (rightMaxStart != -1)
                        {
                            if (rightMaxSum >= crossMaxSum)
                            {
                                maxStart = rightMaxStart;
                                maxEnd = rightMaxEnd;
                                sum = rightMaxSum;
                            }
                            else
                            {
                                maxStart = crossMaxStart;
                                maxEnd = crossMaxEnd;
                                sum = crossMaxSum;
                            }
                        }
                        else
                        {
                            maxStart = crossMaxStart;
                            maxEnd = crossMaxEnd;
                            sum = crossMaxSum;
                        }
                    }
                }
                else
                {
                    if (leftMaxStart != -1)
                    {
                        if (rightMaxStart != -1)
                        {
                            if (leftMaxSum >= rightMaxSum)
                            {
                                maxStart = leftMaxStart;
                                maxEnd = leftMaxEnd;
                                sum = leftMaxSum;
                            }
                            else
                            {
                                maxStart = rightMaxStart;
                                maxEnd = rightMaxEnd;
                                sum = rightMaxSum;
                            }
                        }
                        else
                        {
                            maxStart = leftMaxStart;
                            maxEnd = leftMaxEnd;
                            sum = leftMaxSum;
                        }
                    }
                    else
                    {
                        if (rightMaxStart != -1)
                        {
                            maxStart = rightMaxStart;
                            maxEnd = rightMaxEnd;
                            sum = rightMaxSum;
                        }
                        else
                        {
                            maxStart = -1;
                            maxEnd = -1;
                            sum = 0;
                        }
                    }
                }
            }
        }
    }
}

答案 3 :(得分:0)

起初我也想过使用前缀(已经提到过)

但......我认为有一种更简单的方式:

在我描述给定的问题之前,我解决了一个更简单的问题(我希望输入中的负数):

在具有最大总和的向量中找到子数组:

min_sum=0
max_sum=0
sum=0
for x in elements{
  sum+=x
  if sum < min_sum { min_sum=sum }
  if sum > max_sum { max_sum=sum }
}
result=max_sum-min_sum

我将在一次通过期间为所有k课程执行此操作

min_sum= [ array, k zeros]
max_sum= [ array, k zeros]
sum=0
for x in elements{
  sum+=x
  s = sum % k  // current numberclass
  if sum < min_sum[s] { min_sum[s]=sum }
  if sum > max_sum[s] { max_sum[s]=sum }
}
mx=0
for x in [0:k){
  s=max_sum[x]-min_sum[x]
  if(mx<s) mx=s
}

结果在mx 复杂性O(n+k)