【问题标题】:How to get the equilibrium index of an array in O(n)?如何在 O(n) 中获得数组的平衡索引?
【发布时间】:2015-12-02 22:11:11
【问题描述】:

我在 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 是向量的大小。有没有办法在公式中写下这个顺序,以便我可以在我的函数中应用它?

谢谢


编辑 在一些 cmets 之后,我不得不提到我几天前已经完成了测试,所以我忘了放并提到没有解决方案的部分:它应该返回 -1...我也更新了问题标题。

【问题讨论】:

  • 通过累计求和,您可以在O(n)中完成工作。
  • 我不太理解评论...
  • 如果提到std::partial_sum,评论会更清楚。您一遍又一遍地重复进行相同的计算。也就是说,这有助于意识到您正在寻找LeftHalf(i) = RightHalf(i),因为Total = LeftHalf(i) + RightHalf(i) 是任何i,它遵循Total = 2 * LeftHalf(i)。您根本不需要累积总和。
  • 我认为您应该考虑改写问题,因为到目前为止,所有答案都告诉您如何解决实际问题,但它们并没有真正提到标题中的问题或您提出的问题在你的最后一句话。好像是典型的XY problem...
  • @Gombat:甚至不需要负数。 {1 0 1} 已经是模棱两可了。如果您允许空子集,{0} 已经是不明确的(即,如果输入长度为 1,则没有唯一解,因为当 n!=0 时输入 {n} 没有解)

标签: c++ algorithm vector


【解决方案1】:

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

  1. 计算向量的总和。这给出了两个和(空向量和全向量)
  2. 对于每个元素,将一个元素从满移动到空,这意味着将下一个元素的值从 sum(full) 添加到 sum(empty)。当两个总和相等时,您就找到了索引。

这给出了一个 o(n) 算法而不是 o(n2)

【讨论】:

    【解决方案2】:

    您可以更快地解决问题,而无需在每个步骤中调用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),这就是为什么它对许多元素来说这么慢。

    【讨论】:

    • 你甚至不需要更新s2, if you divide it by 2 up front. (And for odd s2`反正没有整数解。)
    • @MSalters 总和是数组中的数字,可以是任何数字。我不知道除以 2 在这里如何工作。
    • 简单:如果有解决方案 s1 == s2,当然还有 s1+s2 == total,那么 s1 == total/2。基本代数,从一对方程中消除一个变量。为了使更改更小,我不建议将s2 重命名为total
    • std::accumulate() 是函数link
    【解决方案3】:

    回答实际问题:您的序列 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 的幂为单位,而提名者从 j=1 到 i-1 以 2 为步长。但是,这不是您实际问题所需要的,因为你给出的例子有 n=10。显然你不希望 n/4 在那里 - 你的索引必须是整数。

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

    【讨论】:

      【解决方案4】:

      考虑到 MSalters cmets,恐怕另一种解决方案会更好。如果您想使用更少的内存,也许选择的答案就足够了,但是要找到可能的多个解决方案,您可以使用以下代码:

      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)。

      代码只是从左右同时运行,如果它的一侧低于另一侧,则将索引移动得更远。

      【讨论】:

      • 我不认为这适用于所有输入。 if (sum1 &lt;= sum2) sum1 += vec[idx++] 背后的想法显然是给sum1 添加一个值,如果它是两者中较小的一个。但是,vec[idx++] 可以为负数。考虑 sum1 为 5,sum2 为 6,左右半边之间最后一个剩余元素为 -1 的情况。显然,这必须添加到 s2,因此您得到 5 和 5。您的算法将 -1 添加到 5,给出了不正确的 4==6 结果。
      • 对于 { 10, 1, -2, 4, 7 };算法找不到正确的解决方案
      • 您的 O(n)2*O(n) 具有误导性,因为它具有“相同”的复杂性,即使您考虑了这个因素,您也不会在循环中计算额外的 if(所以它可能被视为5*O(n))。
      • @MSalters:你是对的。我编辑了代码以考虑负值。 @Jarod42:我删除了答案中的 2*O(n) 语句 @Alexander:我编辑了代码,现在它应该适用于所有输入,而无需在开始时进行总结。
      • 还是错了。该算法不能回溯。如果剩下 3 个元素都应该去一侧,但它选择了错误的一侧,那么算法就找不到正确的解决方案。并且该算法无法知道剩下 3 个数字的右侧选择(idx1 或 idx2),因为它不考虑中间值,中间值可能有任何值(正或负)。也就是说,左边有数字a b c,对于任何sum1sum2,都有一个值b=(sum2-sum1)-a-c,因此所有3都应该添加到sum1,对于b=(sum1-sum2)-a-c,所有3都应该添加到sum2 .你需要b来选择。
      【解决方案5】:

      你想要你提到的系列的第 nth 个词。那么它会是:

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

      【讨论】:

      • 这与问题有什么关系?
      • @IVlad,他提到他想按特定顺序访问元素,由 n/2、n/4、3n/4、n/8、3n/8、5n/8、7n 给出/8... 其中n 是数组中的元素数。所以,我给了他生成序列的公式,1/2、1/4、3/4、1/8、3/8……等等。看到问题的最后一行,虽然我没有完全理解这个问题,因为他的措辞很糟糕。
      【解决方案6】:

      我在 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

      【讨论】:

        【解决方案7】:

        这是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) 的条件

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-08
          • 1970-01-01
          • 1970-01-01
          • 2017-07-27
          • 2016-01-27
          相关资源
          最近更新 更多