将数组拆分为大约等于总和而不重新排列

Posted

技术标签:

【中文标题】将数组拆分为大约等于总和而不重新排列【英文标题】:Splitting array into about equals sums without rearranging 【发布时间】:2020-09-14 18:04:28 【问题描述】:

所以我有这个让我难过的作业,例如,我给出了一个整数数组 210,50,200,100(可以是任何数量或任何数字),我需要将其分成 N 个部分,这样总和将基本相等,且差异最小,无需对数字进行排序或重新排列,

例如,210,50,200,300 N = 3;它应该分成 210 250 300(最小和最大之间的差异是 90)

我尝试了一个平均 760/3 ~= 253 的程序,并尝试将数字相加,因此它需要 210 并且看起来会增加 50 使其与平均值更接近或更远,因此它添加了 50 冲洗并重复,所以它得到260 200 300 这几乎是好的,但是 200 和 300 之间的差异是 = 100,所以它不是最优的,因为在没有程序的情况下计算你可以获得更好的结果 210 和 300 只有 90 的差异。

TL:DR 需要一种将 int 数组拆分为大约相等部分的方法,不必相同,只要彼此尽可能接近而不改变位置或数组。

如果你不想给我方法,就给我伪代码或基本想法,因为我已经坐了好几个小时了。

        static int[] Skaiciuoti(int[] knygos, int d)

    int[] skyriai = new int[d];
    int n = knygos.Length;
    double sum = knygos.Sum() / d;
    Console.WriteLine(sum);
    int kp = 0;
    for (int i = 0; i < d; i++)
    

        for (int j = kp; j < n; j++)
        
            if (i == skyriai.Length - 1)
            
                skyriai[i] = skyriai[i] + knygos[j];
                kp++;
            
            else if ((skyriai[i] + knygos[j]) < sum || skyriai[i] == 0)
            
                skyriai[i] = skyriai[i] + knygos[j];
                kp++;
            
            else
            
                break;
            
        
    


    return skyriai;


这是我之前尝试过的。

【问题讨论】:

这是一个基本的回溯算法,它为每个号码分配一个组,并根据需要的任何规则检查它是否“有效”。 在您编写一些代码并将其粘贴到您的问题中之前,您可能不会得到答案(这就是本网站的工作方式)。获得平均值是一个好的开始。在我的脑海中,您可能还想计算每个组中可以拥有的最小和最大整数数。 “不改变位置或数组”是什么意思?什么确切地是预期的输出? @RufusL 我的意思是如果数组是 250 210 230 等,我的意思是不改变位置,它必须说同样的话。至于预期的结果,我会说想象它是这样的:你有 N 本书,每本书都有不同的页数和 D 数量的工人。您必须在工作人员之间分配书籍,以便每个工作人员获得与其他工作人员大致相同的页数。结果将是每个工人要阅读多少页(总和); 【参考方案1】:

这是一种方法,类似于您已经尝试过的代码。这个想法是:

    确定 “理想值”,将项目的总和除以我们想要将它们分成的部分数 (resultCount) 使用resultCount 索引创建result 数组 创建一个resultIndex 计数器,以跟踪我们要将当前item 添加到result 数组中的哪个索引 在foreach 循环中,遍历数组,将每个项目添加到result 数组中的当前索引,或者,如果这会使我们超过idealValue,则首先增加我们的索引。李>

例如:

public static int[] Split(int[] input, int resultCount)

    if (input == null) throw new ArgumentNullException(nameof(input));
    if (resultCount > input.Length || resultCount < 1)
        throw new ArgumentOutOfRangeException(nameof(resultCount));

    var idealValue = input.Sum() / resultCount;
    var result = new int[resultCount];
    var resultIndex = 0;

    foreach (var item in input)
    
        // Move to the next index in our result array if the
        // next sum puts us over our expected average amount
        if (result[resultIndex] > 0 &&
            result[resultIndex] + item > idealValue &&
            resultIndex < result.Length - 1)
            resultIndex++;

        result[resultIndex] += item;
    

    return result;


问题在于,如果所有项目都大于我们的“理想值”,那么 result 数组中的最后一个索引将太大,因为所有内容都被推到那里。为了解决这个问题,我们可能需要遍历数组两次,第一次找出每个项目的值是什么,第二次用理想的和填充结果。

考虑输入:

new []  1, 1, 1, 1, 1, 1, 1, 1, 10 , 3  // Split the array into 3 parts

在上面的示例中,sum / parts = 18,所以idealValue = 6,但实际上我们最终会得到一个看起来像: 6, 2, 10 而不是 4, 4, 10 ,后者分布更均匀。

帮助解决此问题的一种方法是删除“异常值”(那些值大于理想值的项目),然后重新计算新的“理想”值。这让我们更接近完美:

public static int[] Split(int[] input, int resultCount)

    if (input == null) throw new ArgumentNullException(nameof(input));
    if (resultCount > input.Length || resultCount < 1)
        throw new ArgumentOutOfRangeException(nameof(resultCount));

    var idealValue = input.Sum() / resultCount;
    var result = new int[resultCount];
    var resultIndex = 0;

    // Recalculate idealValue by removing items over the ideal
    var itemsUnder = input.Where(item => item <= idealValue).ToList();
    idealValue = itemsUnder.Sum() / (resultCount - (input.Length - itemsUnder.Count));

    foreach (var item in input)
    
        // Move to the next index in our result array if the
        // next sum puts us over our expected average amount
        if (result[resultIndex] > 0 &&
            result[resultIndex] + item > idealValue &&
            resultIndex < result.Length - 1)
            resultIndex++;

        result[resultIndex] += item;
    

    return result;

现在输入:

new []  1, 1, 1, 1, 1, 1, 1, 1, 10 , 3  // Split the array into 3 parts

我们首先计算idealValue = 6,然后我们忽略10,然后除以(3 - 1),得到一个 idealValue = 4,我们最终得到一个结果看起来像 4, 4, 10 ,分布更均匀。


这仍然无助于我们最终得到大量较小的数字并被汇总到最后一个索引中的情况(因为没有其他地方可以放置它们)。

例如,如果我们有一个像80, 99, 60, 200, 50, 70, 90 这样的输入,我们将首先计算一个idealValue = 216(因为没有一个项目超过这个数量,所以它在第二次重新计算)。但是当我们通过第一次迭代运行它时,我们最终得到了结果集:179, 60, 410。请注意,接近末尾的所有较小值都被转储到最后一个位置。

处理那个问题的一种方法是在我们生成第一个结果集后重新计算idealValue再次。如果需要通过检查小于idealValue 的项目之间是否有足够大的差异我们可以修复来重新计算结果,我们只想这样做:

public static int[] Split(int[] input, int resultCount)

    if (input == null) throw new ArgumentNullException(nameof(input));
    if (resultCount > input.Length || resultCount < 1)
        throw new ArgumentOutOfRangeException(nameof(resultCount));

    var idealValue = input.Sum() / resultCount;
    var result = new int[resultCount];
    var resultIndex = 0;

    // Recalculate idealValue by removing items over the ideal
    var itemsUnder = input.Where(item => item <= idealValue).ToList();
    idealValue = itemsUnder.Sum() / (resultCount - (input.Length - itemsUnder.Count));

    foreach (var item in input)
    
        // Move to the next index in our result array if the
        // next sum puts us over our expected average amount
        if (result[resultIndex] > 0 &&
            result[resultIndex] + item > idealValue &&
            resultIndex < result.Length - 1)
            resultIndex++;

        result[resultIndex] += item;
    

    // Try to determine if the calculated results can be averaged better 
    var resultsOver = result.Where(item => item > idealValue).ToList();
    var resultsUnder = result.Except(resultsOver).ToList();
    if (resultsUnder.Max() - resultsUnder.Min() > resultsUnder.Count)
    
        // Recalculate idealValue again based on the final results
        // by getting the sum of the difference of the items that are over
        // and dividing it by the total number of items
        idealValue += resultsOver.Select(item => item - idealValue).Sum() / result.Length;

        // Reset our results
        result = new int[resultCount];
        resultIndex = 0;

        foreach (var item in input)
        
            // Move to the next index in our result array if the
            // next sum puts us over our expected average amount
            if (result[resultIndex] > 0 &&
                result[resultIndex] + item > idealValue &&
                resultIndex < result.Length - 1)
                resultIndex++;

            result[resultIndex] += item;
        
    

    return result;

所以现在当我们使用输入 80, 99, 60, 200, 50, 70, 90 时,在我们计算出第一个结果集 (179, 60, 410) 之后,我们通过获取项目结束的数量、获取平均值来重新计算 idealValue,然后将其添加到我们的idealValue,例如:410 - 216 = 194, 194 / 3 = 64, idealValue = 216 + 64 = 280。然后我们重置结果并再次运行它,最终得到:

239, 250, 160

【讨论】:

这正是我以前所做的,是的,如果我以数字为例:80,99,60,200,50,70,90 它将得到结果 179,60,410远非最佳我之前发布的代码与您给我的代码完全相同。 我看到你还在编辑我得到了这个问题,我自己也遇到了,所以我完全放弃了这个方法并尝试了替代方法。但是从你所说的你有一个解决这个问题的方法你介意更详细地解释一下吗? 是的,我是“即时”提出来的 :)。我发布了一个示例,我们通过忽略大于实际“理想”的项目来重新计算“理想值”,这有助于平衡“低点”。 随着您在使用序列 80,99,60,200,50,70,90 分成 3 时的改进,我得到相同的错误结果 179,60,410 我该如何解决这个问题? 如果你能给我和上面的序列一起工作的例子,那就太棒了。

以上是关于将数组拆分为大约等于总和而不重新排列的主要内容,如果未能解决你的问题,请参考以下文章

通过重新排列数组来最大化数组绝对差的总和

数组拆分

数组拆分

《LeetCode之每日一题》:92.减小和重新排列数组后的最大元素

数组拆分I

561.数组拆分I