如何在O(n)中得到数组的平衡指数?

时间:2015-09-07 11:09:50

标签: c++ algorithm vector

我在C ++中做了一个测试,要求一个函数返回一个索引,它将输入向量分成两部分,具有相同的元素总和,例如:对于vec = {1, 2, 3, 5, 4, -1, 1, 1, 2, -1},它可能返回3,因为1 + 2 + 3 = 6 = 4-1 + 1 + 1 + 2-1。所以我完成了返回正确答案的函数:

int func(const std::vector< int >& vecIn)
{
   for (std::size_t p = 0; p < vecin.size(); p++)
   {
      if (std::accumulator(vecIn.begin(), vecIn.begin() + p, 0) == 
          std::accumulator(vecIn.begin() + p + 1, vecIn.end(), 0))
             return p;
   }
   return -1;
}

我的问题是当输入是一个只包含1(或-1)的非常长的向量时,函数的返回很慢。所以我想到从中间开始搜索想要的索引,然后左右移动。但我认为最好的方法是索引采用合并排序算法顺序,即:n / 2,n / 4,3n / 4,n / 8,3n / 8,5n / 8,7n / 8 ...其中n是向量的大小。有没有办法在公式中编写这个顺序,所以我可以在我的函数中应用它?

由于

修改 经过一些评论之后我不得不提到我几天前已经完成了测试,所以我忘了提及没有解决方案的部分:它应该返回-1 ...我已经更新了问题标题。

7 个答案:

答案 0 :(得分:10)

特别针对这个问题,我会使用以下算法:

  1. 计算向量的总和。这给出了两个和(空向量和全向量)
  2. 按顺序为每个元素,将一个元素从full移动到空,这意味着将sum(full)中的next元素的值添加到sum(empty)。当两个总和相等时,您已找到索引。
  3. 这给出了o(n)算法而不是o(n2)

答案 1 :(得分:4)

您可以更快地解决问题而无需在每一步调用std::accumulator

int func(const std::vector< int >& vecIn)
{
   int s1 = 0;
   int s2 = std::accumulator(vecIn.begin(), vecIn.end(), 0);
   for (std::size_t p = 0; p < vecin.size(); p++)
   {
       if (s1 == s2)
           return p;
       s1 += vecIn[p];
       s2 -= vecIn[p];
   }
}

这是O(n)。在每一步中,s1将包含第一个p元素的总和,以及s2其余元素的总和。移动到下一个元素时,可以使用加法和减法更新它们。

由于std::accumulator需要迭代您提供的范围,因此您的算法为O(n^2),这就是为什么它对于许多元素来说太慢了。

答案 2 :(得分:2)

考虑到MSalters的评论,我担心另一种解决方案会更好。如果您想使用更少的内存,可能选择的答案已经足够好了,但要找到可能的多个解决方案,您可以使用以下代码:

static const int arr[] = {5,-10,10,-10,10,1,1,1,1,1};
std::vector<int> vec (arr, arr + sizeof(arr) / sizeof(arr[0]) );

// compute cumulative sum
std::vector<int> cumulative_sum( vec.size() );
cumulative_sum[0] = vec[0];
for ( size_t i = 1; i < vec.size(); i++ )
{ cumulative_sum[i] = cumulative_sum[i-1] + vec[i]; }

const int complete_sum = cumulative_sum.back();

// find multiple solutions, if there are any
const int complete_sum_half = complete_sum / 2; // suggesting this is valid...
std::vector<int>::iterator it = cumulative_sum.begin();
std::vector<int> mid_indices;
do {
  it = std::find( it, cumulative_sum.end(), complete_sum_half );

  if ( it != cumulative_sum.end() )
  { mid_indices.push_back( it - cumulative_sum.begin() ); ++it; }

} while( it != cumulative_sum.end() );

for ( size_t i = 0; i < mid_indices.size(); i++ )
{ std::cout << mid_indices[i] << std::endl; }

std::cout << "Split behind these indices to obtain two equal halfs." << std::endl;

这样,您就可以获得所有可能的解决方案。如果没有解决方案将矢量分成两个相等的半部分,则mid_indices将保留为空。 同样,您只需要将每个值相加一次。

我的建议如下:

static const int arr[] = {1,2,3,5,4,-1,1,1,2,-1};
std::vector<int> vec (arr, arr + sizeof(arr) / sizeof(arr[0]) );

int idx1(0), idx2(vec.size()-1);
int sum1(0), sum2(0);
int idxMid = -1;
do {
  // fast access without using the index each time.
  const int& val1 = vec[idx1];
  const int& val2 = vec[idx2];

  // Precompute the next (possible) sum values. 
  const int nSum1 = sum1 + val1;
  const int nSum2 = sum2 + val2;

  // move the index considering the balanace between the 
  // left and right sum.
  if ( sum1 - nSum2 < sum2 - nSum1 )
  { sum1 = nSum1; idx1++; }
  else
  { sum2 = nSum2; idx2--; }

  if ( idx1 >= idx2 ){ idxMid = idx2; }

} while( idxMid < 0 && idx2 >= 0 && idx1 < vec.size() );

std::cout << idxMid << std::endl;

无论有多少值,它都会只添加一次值。这样它的复杂性仅为O(n)而不是O(n ^ 2)。

代码只是从左右同时运行,如果它的一侧比另一侧低,则进一步移动索引。

答案 3 :(得分:2)

回答实际问题:您的序列n / 2,n / 4,3n / 5,n / 8,3n / 8可以改写为

1*n/2
1*n/4 3*n/4
1*n/8 3*n/8 5*n/8 7*n/8
...

也就是说,分母从i = 2开始以2的幂运行,并且分子以2的步长从j = 1运行到i-1。但是,这不是您实际问题所需要的,因为你给出的例子有n = 10。显然你不想要n / 4 - 你的指数必须是整数。

这里最好的解决办法是递归。给定范围[b,e],选择中间值(b + e / 2)并将新范围设置为[b,(b + e / 2)-1]和[(b + e / 2)= 1 ,e]。当然,专长范围长度为1或2。

答案 4 :(得分:1)

你想要你提到的系列的n th 术语。那就是:

numerator:   (n - 2^((int)(log2 n)) ) *2 + 1
denominator:  2^((int)(log2 n) + 1) 

答案 5 :(得分:1)

我在Codility测试中遇到了同样的问题。上面有一个类似的答案(没有通过一些单元测试),但在代码段下面的测试成功。

#include <vector>
#include <numeric>
#include <iostream>

using namespace std;

// Returns -1 if equilibrium point is not found
// use long long to support bigger ranges
int FindEquilibriumPoint(vector<long> &values) {
    long long lower = 0;
    long long upper = std::accumulate(values.begin(), values.end(), 0);

    for (std::size_t i = 0; i < values.size(); i++) {

        upper -= values[i];

        if (lower == upper) {
            return i;
        }

        lower += values[i];
    }

    return -1;
}


int main() {
        vector<long> v = {-1, 3, -4, 5, 1, -6, 2, 1};

        cout << "Equilibrium Point:" << FindEquilibriumPoint(v) << endl;

        return 0;
}
  

输出   平衡点:1

答案 6 :(得分:0)

这是Javascript中的算法:

function equi(arr){

var N = arr.length;
if (N == 0){ return -1};

var suma = 0;
for (var i=0; i<N; i++){
    suma += arr[i];
}

var suma_iz = 0;
for(i=0; i<N; i++){
    var suma_de = suma - suma_iz - arr[i];
    if (suma_iz == suma_de){
        return i};
    suma_iz += arr[i];
}

return -1;

}

如您所见,此代码满足O(n)

的条件