什么是更有效的算法来均衡矢量?

时间:2011-06-12 20:38:45

标签: algorithm performance

给定n个整数类型元素的向量,产生最小数量的转换步骤的效率更高的算法是什么,导致所有元素等于的向量,知道:

  • 在一个步骤中,您最多可以从元素到其邻居传递一个点([0,3,0] - > [1,2,0]可以但不是[0,3,0] - > [1,1,1])。
  • 在一个步骤中,一个元素可以获得2个点:一个来自左边邻居,一个来自右边([3,0,3] - > [2,2,2])。
  • 第一个元素和最后一个元素分别只有一个邻居,第二个元素和n-1个元素。
  • 任何一个元素都不能为负数。

示例:

Given :
 0, 3, 0
Then 2 steps are required :
 1, 2, 0
 1, 1, 1

Given :
 3, 0, 3
Then 1 step is required :
 2, 2, 2

Given :
 4, 0, 0, 0, 4, 0, 0, 0
Then 3 steps are required :
 3, 1, 0, 0, 3, 1, 0, 0
 2, 1, 1, 0, 2, 1, 1, 0
 1, 1, 1; 1, 1, 1, 1, 1

我当前的算法基于元素每一边的整数之和。但我不确定它是否产生了最小的步骤。

仅供参考,问题是代码竞赛的一部分(由Criteo http://codeofduty.criteo.com创建)已结束。

3 个答案:

答案 0 :(得分:6)

这是一种方式。您知道数组的总和,因此您知道每个单元格中的目标编号。 因此,您还知道每个子阵列的目标总和。 然后遍历数组,并在每一步做出决定:

  1. 向左移动1:如果前一个元素的总和小于预期值。
  2. 向右移动1:如果当前元素的总和超出预期
  3. 不做任何事情:如果上述两种情况都是假的
  4. 重复此操作,直到不再进行任何更改(即您只为每个元素应用3)。

        public static int F(int[] ar)
        {
            int iter = -1;
            bool finished = false;
            int total = ar.Sum();
    
            if (ar.Length == 0 || total % ar.Length != 0) return 0; //can't do it
            int target = total / ar.Length;
    
            int sum = 0;
    
            while (!finished)
            {
                iter++;
                finished = true;
                bool canMoveNext = true;
    
                //first element
                if (ar[0] > target)
                {
                    finished = false;
                    ar[0]--;
                    ar[1]++;
    
                    canMoveNext = ar[1] != 1;
                }
    
                sum = ar[0];
                for (int i = 1; i < ar.Length; i++)
                {
                    if (!canMoveNext)
                    {
                        canMoveNext = true;
                        sum += ar[i];
                        continue;
                    }
    
                    if (sum < i * target && ar[i] > 0)
                    {
                        finished = false;
                        ar[i]--;
                        ar[i - 1]++;
                        sum++;
                    }
                    else if (sum + ar[i] > (i + 1) * target && ar[i] > 0) //this can't happen for the last element so we are safe
                    {
                        finished = false;
                        ar[i]--;
                        ar[i + 1]++;
    
                        canMoveNext = ar[i + 1] != 1;
                    }
    
                    sum += ar[i];
                }
            }
    
            return iter;
        }
    

答案 1 :(得分:4)

我有个主意。我不确定它会产生最佳结果,但感觉就像它可以。

假设初始向量是N大小的向量V。你需要另外两个N大小的矢量:

  • L向量中,您从左侧开始对元素求和:L[n] = sum(i=0;i<=n) V[n]
  • R向量中,您从右侧开始对元素求和:R[n] = sum(i=n;i<N) V[n]

您最终需要最后一个特定值:V的所有元素的总和应该等于k*Nk是一个整数。你有L[N-1] == R[0] == k*N

我们来看L向量。这个想法是,对于任何n,考虑将V向量分为两部分,一部分从0到n,另一部分包含其余部分。如果L[n]<n*k,那么你必须用第二部分的值“填充”第一部分。反之亦然L[n]>n*k如果L[i]==i*k,那么祝贺,问题可以细分为两个子问题!没有理由将第二个向量中的任何值传递给第一个向量,反之亦然。

然后,算法很简单:对于n的每个值,检查L[n]-n*kR[n]-(N-n)*k的值并相应地采取行动。只有一种特殊情况,如果L[n]-n*k>0 R[n]-(N-n)*k>0(V [n]处的值较高),则必须在两个方向清空它。只需随意选择转移的方向即可。

当然,不要忘记相应地更新LR

编辑:事实上,您似乎只需要L向量。这是一个简化的算法。

  • 如果L[n]==n*k,则不要做任何事情
  • 如果L[n]<n*k,则将一个值从V[n+1]转移到V[n](当然,如果V[n+1]> 0)
  • 如果L[n]>n*k,则将一个值从V[n]转移到V[n+1](当然,如果V[n]> 0)

并且(特殊情况)如果您被要求从V[n]转移到V[n-1]V[n+1],只需随机转移一次,就不会更改最终结果。< / p>

答案 2 :(得分:1)

感谢Sam Hocevar,以下是对fiver的一个替代实现:

public static int F(int[] ar)
{
    int total = ar.Sum();

    if (ar.Length == 0 || total % ar.Length != 0) return 0; //can't do it
    int target = total / ar.Length;

    int[] left = new int[ar.Length];
    int[] right = new int[ar.Length];
    int maxshifts = 0;
    int delta = 0;
    for (int i = 0; i < ar.Length; i++)
    {
        left[i] = delta < 0 ? -delta : 0;
        delta += ar[i] - target;
        right[i] = delta > 0 ? delta : 0;
        if (left[i] + right[i] > maxshifts) {
            maxshifts = left[i] + right[i];
        }    
    }

    for (int iter = 0; iter < maxshifts; iter++)
    {
        int lastleftadd = -1;

        for (int i = 0; i < ar.Length; i++)
        {
            if (left[i] != 0  && ar[i] != 0)
            {
                ar[i]--;
                ar[i - 1]++;
                left[i]--;
            }
            else if (right[i] != 0 && ar[i] != 0
                              && (ar[i] != 1 || lastleftadd != i))
            {
                ar[i]--;
                ar[i + 1]++;
                lastleftadd = i + 1;
                right[i]--;
            }
        }
    }

    return maxshifts;
}