将数组划分为 k 个连续分区,使得最大分区的总和最小

Posted

技术标签:

【中文标题】将数组划分为 k 个连续分区,使得最大分区的总和最小【英文标题】:Divide array into k contiguos partitions such that sum of maximum partition is minimum 【发布时间】:2017-02-02 02:08:08 【问题描述】:

这里的最大和子集是给出最大和的 k 个子集之一 例如:arr = [10,5,3,7] 和 k = 2 将 arr 划分为 k 个子集的可能方法是

  10,[5,3,7],[10,5],[3,7,[10,5,3],7

[10,5],[3,7 is the optimal one.

编辑:它相当于 https://www.codechef.com/DI15R080/problems/MINMAXTF

【问题讨论】:

请回答,如有疑问请写在cmets中。 有什么问题?你能显示一些代码吗,因为很难理解你卡在哪里了。 阵列有多大?数字能有多大? 我不知道该怎么做。相当于这个问题:codechef.com/DI15R080/problems/MINMAXTF 您链接到的问题不同。它想找到一种方法来划分数组,使得总和最大的子集最小。如果仍然无法解决,请编辑问题。 【参考方案1】:

假设您知道答案是 x,这意味着最大子集的总和等于 x。你可以通过贪心算法O(n)来验证这个假设。 (从左到右遍历数组并选择项目,直到该子集的总和小于 x)。现在您可以对 x 进行二分搜索并找到 x 的最小值。该算法的复杂度为O(nlogn)

【讨论】:

嘿,你的复杂度是 O(n*log(sum(array)))。对吗? 还是O(nlogn) @sudomakeinstall2 不错的解决方案。您能否添加更多解释为什么这种贪婪有效? 当然。这样想:你有一些袋子都有 x 容量。您希望将物品(从左到右)放入这些袋子中,并且希望尽量减少袋子的数量。贪婪的方法将解决这个问题。您可以轻松查看当前袋子是否有足够的容量容纳您应该放入的当前物品,否则可能会增加所需的袋子数量。您可以将上述问题的贪心部分归结为这个袋子问题。 @A.Sarid 它不是 O(n log n)。 sum(array) 可以远大于 n。【参考方案2】:

这是一个对样本空间进行二分搜索的示例。

int min_max_sum(std::vector<int> & a, int K)         

    int n = a.size();    
    long long high = 0, low = 0, mid = 0;

    for (int i = 0; i < n; ++i) 
        high += a[i];
        low = max(a[i], low);
    

    while(low <= high) 
        mid = (low+high)/2;

        long long part_sum = 0;
        int parts = 1;
        for (int i = 0; i < n; ++i) 
            if (part_sum + a[i] > mid) 
                part_sum = 0;
                parts++;
             else 
                part_sum += a[i];
            
        

        // if no. of parts in less than (or equal to) K then mid needs to (,or can) be more constrained by reducing upper limit
        if (parts <= K) 
            high = mid - 1;
         else  
            low = mid + 1;
        
    

    return mid;

复杂度:O(n log(sum(array)))。

但由于对数比线性要好很多倍,因此这种复杂性非常好。

最坏情况复杂度:O(n log(INT_MAX * n))=O(32 n +n log(n))=O(n log(n))。

【讨论】:

如果有条件,part_sum =0 应在 for 循环内更正为 part_sum=a[i]。测试 arr=[1 3 2 4 10 8 4 2 5 3] 和 K = 4,答案应该是 12【参考方案3】:

让我们从一个例子开始。假设N=5,k=3(假设除法后会有三部分)。设我们的数组是1,2,8,4,9。我们可以观察到,如果 k 等于 1,则最大分区的总和将为 sum(所有数组元素),即 24,如果 k=5,则总和最大分区将是最大值(所有数组元素),即 9。现在,我们可以观察到随着 k 的增加,最大分区的最小值之和减小。我们的算法将在此过程中借助二分搜索。可是怎么办??????通过查看问题——我们可以看到,我们必须找到最大值的最小值,这会引起诸如“FFFFTTTTTTTT”之类的问题的感觉,我们必须找到第一个 T。我们将做完全相同的事情,但会采取一个函数的帮助。(查看代码并从这里并行回答)..当提供最大分区的最小和时,辅助函数将找到 K 的值。我们将初始化 low=max(all数组元素),这里low=9和high=sum(所有数组元素)即,high=24。因此,mid=16,所以,对于min_max_dist=16,我们的帮助函数将返回所需的k个数。如果k>K,这意味着我们的 min_max_dist 可以容纳更多的值。所以,如果 kK ,这意味着在较少的分区数下,我们可以达到 min_max_dist,但是我们可以做更多的分区,因此我们可以将高值降低到中间。所以,我们的代码将在下面的示例中执行方法:-

    low           high               mid       number_of_k
    9             24                 16         9
    9             16                 12         2
    9             12                 10         4
    11            12                 11         3

最后我们的结果->low=11 将被返回..

    #include <bits/stdc++.h>
    #define ll long long int
    using namespace std;

    ll number_of_k(ll arr[],ll n,ll minimum_max__dist)
       ll sum=0;
       ll k=1;
       for(ll i=0;i<n;i++)
          sum+=arr[i];
         if(sum > minimum_max__dist)
           sum=arr[i];
            k++;
          
      
    return k;
   

    ll Max(ll arr[], ll n)
    
       ll max1 = -1;
       for (ll i = 0; i < n; i++)
          if (arr[i] > max1)
              max1 = arr[i];
      return max1;
    

    ll Sum(ll arr[], ll n)
    
      ll sum = 0;
       for (int i = 0; i < n; i++)
       sum += arr[i];
       return sum;
     

       ll min_max_bin_search(ll arr[],ll n,ll k)
         ll low=Max(arr,n);
         ll high=Sum(arr,n);
         ll mid;
while(low<high)
     mid=low+(high-low)/2;
    if(number_of_k(arr,n,mid)<=k)
       high=mid;
    else
        low=mid+1;
     
  return low;
  


 int main()
 
      ll n,k;
       cin>>n>>k;
       ll arr[n];
       for(ll i=0;i<n;i++)cin>>arr[i];
       cout<<min_max_bin_search(arr,n,k)<<endl;

   return 0;
 

这个算法的时间复杂度是->O(nlogn)

阅读这篇关于二分搜索的文章-> https://www.topcoder.com/community/data-science/data-science-tutorials/binary-search/ 和 解决这个问题->http://www.spoj.com/problems/AGGRCOW/

【讨论】:

你的解释有点破,但它确实帮助我了解那里发生的事情,谢谢!【参考方案4】:

您可以在此处找到有关基于动态编程的解决方案的优秀文章:https://www.geeksforgeeks.org/painters-partition-problem/ 和 它的复杂度是 O(k*N^2)。

为了获得更好的解决方案,您可以使用其他人建议的二进制搜索方法。您可以在此处找到使用二分查找的更详细解决方案:https://www.geeksforgeeks.org/painters-partition-problem-set-2/,其复杂度为 O(NlogN)

【讨论】:

【参考方案5】:

这可以使用动态规划来解决:

让我们首先定义DP[n,m] 为将子数组C[1..n] 划分为m 部分的最佳解决方案。每个部分至少有一个元素。

DP[n,1] = sum(C1:Cn)
DP[n,n] = max(C1:Cn)
DP[n,m] = min( sum(Ck:Cn) + DP[k-1,m-1] )
          where k goes from m to n

说明:DP[n,1] - 基本情况,当分区数为 1 时,只有一种方法 - 剩下所有元素(从 1 到 n)。DP[n,n] - 每当分区数等于数组中剩余的元素数时,只有一种合法的划分方式 - 每个元素位于不同的分区中,因此总和最大的分区是数组中的最大元素array.DP[n,m] - 这是主要的解决方案。我们不知道下一个分区将有多少元素,因此我们需要遍历所有选项并从中获得最小值。

【讨论】:

这个算法可以工作,但不适用于这个问题,因为 NK 是 10^5 并且这个算法的复杂度是 O(N*K )。 首先,OP 没有说明 N 和 K 的大小以及复杂性。其次,这并不意味着它不起作用,它可能不是最有效的解决方案,但它确实有效。 应该是 max(sum(Ck:Cn),DP[k-1,m-1]) 而不是 sum(Ck:Cn) + DP[k-1,m-1]【参考方案6】:

分裂只是一个蛮力问题。您必须专注于要最小化的功能。所以你必须最小化的是与平均值的偏差。

int sum_arr(int *arr,int size)

    int res=0;
    while (size-->0)
       res+=*arr++;
   return res;

int   minimize( const int *array, int size)

    int i,j,cur_i;
    double dev, cur_min,avg=(double)sum_arr(array,size)/size;

    for (i=1;i<size-1;i++)
      
         dev=abs(avg-sum_arr(array,i));
         dev+=abs(avg-sum_arr(array+i,size-i);
         if (dev<cur_min)
         
              cur_min=dev;
               cur_i=i;
         
      
     return cur_i;

一个简单的代码是:(未测试)

【讨论】:

我不认为划分是只是一个蛮力问题。 这不是蛮力,而是最小的问题。如果您想要一个线性解决方案并且内存不是问题,请使用部分和创建其他两个数组。您计算一次总和,并且 i 元素的总和由 sum(i)=sum(i-1)+i mehanweile 给出,剩余的总和由 sum(n-i)=sum(n-i)-i 给出。复杂度 O(3*n)== O(n) 我在您的解决方案中找不到变量 k

以上是关于将数组划分为 k 个连续分区,使得最大分区的总和最小的主要内容,如果未能解决你的问题,请参考以下文章

解决分区问题的递归回溯算法

我如何将整数数组划分为N个分区?

多路分区的变化

找到最大和连续子数组,使得子数组的长度小于等于 k?

将数组(元素组合)划分为自定义分区的所有方式

将一个数组划分为 K 个差异最小的子数组