计算数组元素中不同绝对值的数量

时间:2011-08-21 04:09:11

标签: c++ algorithm performance

我被问到一个面试问题,以找出数组元素中不同绝对值的数量。我想出了以下解决方案(在C ++中),但是面试官对代码的运行时效率不满意。

  1. 我将非常感谢如何提高此代码的运行时效率?
  2. 另外,我如何计算下面代码的效率? for循环执行A.size()次。但是我不确定STL std::find的效率(在更糟糕的情况下它可能是O(n)所以这使得代码O(n²)
  3. 代码是:

    int countAbsoluteDistinct ( const std::vector<int> &A ) {
      using namespace std;
      list<int> x;
    
      vector<int>::const_iterator it;
      for(it = A.begin();it < A.end();it++)
        if(find(x.begin(),x.end(),abs(*it)) == x.end())
          x.push_back(abs(*it));
      return x.size();
    }
    

13 个答案:

答案 0 :(得分:17)

建议设置代码的替代代码。

请注意,我们不想改变调用者的向量,我们采用值。让编译器为我们复制比制作我们自己的更好。如果可以破坏它们的值,我们可以采用非const引用。

#include <vector>
#include <algorithm>
#include <iterator>

#include <cstdlib>

using namespace std;

int count_distinct_abs(vector<int> v)
{
    transform(v.begin(), v.end(), v.begin(), abs); // O(n) where n = distance(v.end(), v.begin())
    sort(v.begin(), v.end()); // Average case O(n log n), worst case O(n^2) (usually implemented as quicksort.
    // To guarantee worst case O(n log n) replace with make_heap, then sort_heap.

    // Unique will take a sorted range, and move things around to get duplicated
    // items to the back and returns an iterator to the end of the unique section of the range
    auto unique_end = unique(v.begin(), v.end()); // Again n comparisons
    return distance(v.begin(), unique_end); // Constant time for random access iterators (like vector's)
}

这里的优势在于,如果我们决定按值计算,我们只会分配/复制一次,其余的都是就地完成的,同时仍然会给您O(n log n)的平均复杂度v 1}}。

答案 1 :(得分:4)

std::find()是线性的(O(n))。我将使用一个有序的关联容器来处理这个问题,特别是std::set

#include <vector>
#include <set>
using namespace std;

int distict_abs(const vector<int>& v)
{
   std::set<int> distinct_container;

   for(auto curr_int = v.begin(), end = v.end(); // no need to call v.end() multiple times
       curr_int != end;
       ++curr_int)
   {
       // std::set only allows single entries
       // since that is what we want, we don't care that this fails 
       // if the second (or more) of the same value is attempted to 
       // be inserted.
       distinct_container.insert(abs(*curr_int));
   }

   return distinct_container.size();
}

这种方法仍然存在一些运行时损失。随着容器大小的增加,使用单独的容器会产生动态分配的成本。您可以在适当的位置执行此操作而不会出现此惩罚,但是在此级别的代码中,有时更好的是清晰明确,并让优化器(在编译器中)完成其工作。

答案 2 :(得分:3)

是的,这将是O(N 2 ) - 你最终会对每个元素进行线性搜索。

一些相当明显的替代方案是使用std::setstd::unordered_set。如果您没有C ++ 0x,则可以将std::unordered_set替换为tr1::unordered_setboost::unordered_set

std::set中的每个插入都是O(log N),因此您的整体复杂度为O(N log N)。

使用unordered_set,每个插入都具有恒定(预期)的复杂性,从而给出整体的线性复杂性。

答案 3 :(得分:2)

基本上,用std :: set替换你的std :: list。如果您正确执行操作,这将为您提供O(log(set.size()))搜索+ O(1)插入。另外,为了提高效率,缓存abs(* it)的结果是有意义的,尽管这只会产生最小的(可忽略的)效果。这种方法的效率与你可以得到的效果差不多,没有使用非常好的哈希(std :: set使用bin-trees)或更多关于向量中值的信息。

答案 4 :(得分:2)

由于我对之前的答案不满意,今天是我的。你的初步问题没有提到你的矢量有多大。假设您的std::vector<>非常大且重复很少(为什么不呢?)。这意味着使用另一个容器(例如std::set<>)将基本上复制您的内存消耗。为什么要这样做,因为你的目标只是计算不重复。

我喜欢@Flame回答,但我对std::unique的电话不满意。您花了很多时间仔细地对矢量进行排序,然后简单地丢弃已排序的数组,然后再重新使用它。

我在STD库中找不到任何真正优雅的东西,所以这是我的提案(std::transform + std::abs + std::sort的混合,但之后没有触及排序的数组)

// count the number of distinct absolute values among the elements of the sorted container
template<class ForwardIt>
typename std::iterator_traits<ForwardIt>::difference_type 
count_unique(ForwardIt first, ForwardIt last)
{
  if (first == last)
    return 0;

  typename std::iterator_traits<ForwardIt>::difference_type 
    count = 1;
  ForwardIt previous = first;
  while (++first != last) {
    if (!(*previous == *first) ) ++count;
    ++previous;
  }
  return count;
}

奖励点适用于前进迭代器:

#include <iostream>
#include <list>
int main()
{
  std::list<int> nums {1, 3, 3, 3, 5, 5, 7,8};
  std::cout << count_unique( std::begin(nums), std::end(nums) ) << std::endl;

  const int array[] = { 0,0,0,1,2,3,3,3,4,4,4,4};
  const int n = sizeof array / sizeof * array;
  std::cout << count_unique( array, array + n ) << std::endl;
  return 0;
}

答案 5 :(得分:1)

两点。

  1. std :: list对搜索非常不利。每次搜索都是O(n)。

  2. 使用std :: set。 Insert是对数的,它删除重复并进行排序。插入每个值O(n log n),然后使用set :: size查找多少个值。

  3. 编辑:

    要回答问题的第2部分,C ++标准规定了容器和算法操作的最坏情况。

    Find:由于你使用的是带有迭代器的find的自由函数版本,它不能假定传入序列的任何内容,它不能假设范围是有序的,所以它必须遍历每个项目,直到它找到一个匹配,即O(n)。

    如果你正在使用set::find,那么这个成员find可以利用集合的结构,并且它的性能必须是O(log N),其中N是集合的大小。 / p>

答案 6 :(得分:0)

首先回答您的第二个问题,是的,代码为O(n^2),因为find的复杂性为O(n)

您可以选择改进它。如果数字范围很小,您可以设置足够大的数组并在迭代源数据时递增计数。如果范围较大但是稀疏,则可以使用某种哈希表来进行计数。这两个选项都是线性复杂性。

否则,我会做一次迭代来获取每个项目的abs值,然后对它们进行排序,然后你可以在一个额外的传递中进行聚合。这里的复杂性为n log(n)。其他通行证与复杂性无关。

答案 7 :(得分:0)

我认为std::map也可能很有趣:

int absoluteDistinct(const vector<int> &A) 
{
    map<int, char> my_map;

    for (vector<int>::const_iterator it = A.begin(); it != A.end(); it++)
    {
        my_map[abs(*it)] = 0;
    }

    return my_map.size();
}

答案 8 :(得分:0)

正如@Jerry所说,为了改善大多数其他答案的主题,而不是使用std :: map或std :: set你可以使用std :: unordered_map或std :: unordered_set(或提升当量)。

这会减少O(n lg n)或O(n)的运行时间。

另一种可能性,取决于给定数据的范围,您可能能够做基数排序的变体,尽管问题中没有任何内容可以立即表明这一点。

答案 9 :(得分:0)

使用Radix样式排序对列表进行排序,以获得O(n)效率。比较相邻的值。

答案 10 :(得分:0)

最好的方法是自定义快速排序算法,这样当我们分区时,只要得到两个相等的元素,然后用范围中的最后一个元素覆盖第二个副本,然后减小范围。这将确保您不会两次处理重复元素。快速排序完成后,元素的范围也是答案 复杂性仍然是O(n * Lg-n)但是这应该保存至少两次遍历数组。

此外,节省与重复百分比成正比。想象一下,如果他们扭曲原始的questoin,'说90%的元素是重复'...

答案 11 :(得分:0)

另一种方法:

节省空间:使用哈希映射。 O(logN)* O(n)用于插入,只保留成功插入的元素数。

节省时间:使用哈希表O(n)进行插入,只保留成功插入的元素数量。

答案 12 :(得分:0)

您的代码中嵌套了循环。如果您将扫描整个阵列上的每个元素,它将为您提供O(n ^ 2)时间复杂度,这在大多数情况下是不可接受的。这就是Merge SortQuick sort算法来节省处理周期和机器工作的原因。我建议您浏览建议的链接并重新设计您的计划。