【问题标题】:Find a subarray whose sum is divisible by a number K the subarray should be of maximum sum of all possible subarrays找到一个子数组,其总和可以被数字 K 整除,该子数组应该是所有可能子数组的最大和
【发布时间】:2013-08-01 21:11:13
【问题描述】:

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

【问题讨论】:

  • 修改Kadane's algorithm 可能会有所帮助。
  • 我也想过,但没有实现。考虑{3,2,2,3} k=4。你将如何检查可分性?
  • 您提到了 +ve 和 -ve - 这是否意味着数组具有有界元素?
  • @ZiyaoWei 不会的

标签: algorithm


【解决方案1】:

这个问题的关键词是前缀和。

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

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

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

我们关心的属性是总和和可被 K 整除。现在要找到最大总和子数组,我们查看每个元素一次。当我们查看每个元素一次时,我们会做 4 件事:

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

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

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

  4. 我们检查这个数组的总和是否大于我们当前找到的最大数组,以及什么时候,将此数组设置为我们新的最高得分者。

细节非常复杂,因为您必须寻找正确的索引并且您必须关心所有异常情况(比如什么都没有找到时......),但我想您会了解算法的想法.它的运行时间是 O(n),并且由于前缀 sum,它应该适用于负数和正数。

【讨论】:

  • 你能用一些例子解释一下吗?我认为在解决K-rem[i] 变成索引i 本身时存在一些问题。取a={2,1,3,2}k=6,然后是prefix_sum={2,3,6,8} rem={2,3,0,2} max_start={2,_,3,1,_,_}。现在当i=1rem[i]=3 和 K-rem[i] 即6-3=3 现在我们转到max_start[3] 并看到那里的值是i i.e 1 本身。我有点困惑。
  • 如前所述,细节会变得非常复杂。 (我上面的顺序不太好,当我使用 other 时会更清楚)在对元素进行所有其他操作后,将 rem 值添加到 max_start 中,这意味着当时 rem[1] 仍然空,结果就是没有以元素1结尾的有效子数组。(atm我没有那么多时间,但我会添加一个例子让你看到它)。
  • 如果你在第一个元素之前注入一个 0 会简化事情,之后你应该寻找两个 K ;你必须为每个 numberclass [0:K)...我认为这个算法的复杂性是:O(n*k)
  • 请您解释一下这句话 - 我们知道子数组是有效的当且仅当 rem[start_subarray] + rem[end_subarray] == K。对于这个数组 {4,2,2,2,1} 和 K = 7 - rem[] = {4,6,1,3,4}。 rem[start_subarray] + rem[end_subarray] = 10 不等于 7。
【解决方案2】:

如果不是负数,则每个总和可被 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

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

【讨论】:

  • 在采访中这也将被视为蛮力。
  • 邦德,是的,这是一种蛮力方法。没有看到 EDIT。
【解决方案3】:

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

如果 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

我为此编写了以下 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;
                        }
                    }
                }
            }
        }
    }
}

【讨论】:

    【解决方案4】:

    一开始我也想过使用前缀(前面已经提到过)

    但是...我认为有一个更简单的方法:

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

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

    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)

    【讨论】:

      猜你喜欢
      • 2012-10-16
      • 1970-01-01
      • 2012-07-31
      • 2013-05-12
      • 2013-03-05
      • 2020-06-26
      • 2022-12-12
      • 2013-02-24
      • 1970-01-01
      相关资源
      最近更新 更多