我被问到一个面试问题,以找出数组元素中不同绝对值的数量。我想出了以下解决方案(在C ++中),但是面试官对代码的运行时效率不满意。
for
循环执行A.size()
次。但是我不确定STL std::find
的效率(在更糟糕的情况下它可能是O(n)
所以这使得代码O(n²)
?代码是:
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();
}
答案 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::set
或std::unordered_set
。如果您没有C ++ 0x,则可以将std::unordered_set
替换为tr1::unordered_set
或boost::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)
两点。
std :: list对搜索非常不利。每次搜索都是O(n)。
使用std :: set。 Insert是对数的,它删除重复并进行排序。插入每个值O(n log n),然后使用set :: size查找多少个值。
要回答问题的第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 Sort和Quick sort算法来节省处理周期和机器工作的原因。我建议您浏览建议的链接并重新设计您的计划。