【问题标题】:Time complexity difference between two containsDuplicates algorithms两种 containsDuplicates 算法的时间复杂度差异
【发布时间】:2016-03-24 17:58:44
【问题描述】:

我完成了leetcode algorithm 的两个版本,我想知道我的复杂性分析是否正确,尽管以毫秒为单位的在线提交时间并不能准确显示。目标是将数字向量作为参考,如果包含重复值则返回 true,否则返回 false。

两种最直观的方法是:

1.) 对向量进行排序并扫描到倒数第二个,查看是否有任何相邻元素相同,如果相同则返回 true。

2.) 使用哈希表并插入值,如果表中已存在键,则返回 true。

我首先完成了第一个版本,它很快,但是看看排序例程将如何使用O(nlog(n)) 和哈希表插入 & map.count()s would 制作第二个版本O(log(n) + N) = O(N) 我想对于非常大的数据集,哈希版本会更快。

在在线评审中,我被证明是错误的,但我认为他们没有使用足够大的数据集来抵消std::map 开销。所以我运行了很多测试,重复填充向量到 0 到 10000 之间的大小,以 2 递增,添加 0 到 20000 之间的随机值。我将输出通过管道传输到 csv 文件并在 linux 上绘制,这是我得到的图像.

the provided image 是否真正向我展示了 O(N)O(nlog(n)) 算法之间的区别?我只是想确保我对这些的复杂性分析是正确的?

以下是运行的算法:

bool containsDuplicate(vector<int>& nums) {
  if(nums.size() < 2) return false;
  sort(nums.begin(), nums.end());
  
  for(int i = 0; i < nums.size()-1; ++i) {
    if(nums[i] == nums[i+1]) return true;
  }
  return false;
}
// Slightly slower in small cases because of data structure overhead I presume
bool containsDuplicateWithHashing(vector<int>& nums) {
  map<int, int> map;
  for (int i = 0; i < nums.size(); ++i) {
    if(map.count(nums[i])) return true;
    map.insert({nums[i], i});
  }
  return false;
}

【问题讨论】:

  • 您可以使用set 代替map,并检查来自insert 的返回而不是调用countmap 是红/黑树,而不是散列容器。 (使用unordered_map/unordered_set。)

标签: c++ algorithm sorting hashmap time-complexity


【解决方案1】:

std::map is sorted, and involves O(log n) cost for each insertion and lookup,因此“无重复”情况下(或“向量末尾附近的第一个重复”情况下)的总成本与排序和扫描类似:O(n log n);它通常在内存中分散,因此开销很容易高于优化的std::sort

如果重复很常见,它会显得更快;如果您通常在前 10 个元素中找到重复项,那么输入是否有 10,000 个元素并不重要,因为在您遇到重复项并退出之前,map 没有时间增长。只是一个只有在成功时才能正常工作的测试对于一般用途来说并不是一个很好的测试(如果重复如此常见,那么测试似乎有点傻);您希望在包含重复和不包含重复的情况下都有良好的性能。

如果您希望比较算法复杂度明显不同的方法,请尝试使用 std::unordered_set 替换基于地图的解决方案(insert 返回密钥是否也已存在,因此您可以减少后续查找的工作量通过一个插入到每个循环上的一个组合插入和查找),它具有平均情况 O(1) 插入和查找,对于 O(n) 重复检查复杂性。

仅供参考,另一种方法是O(n log n),但使用类似排序的策略,在早期发现重复项时使用快捷方式,将使用std::make_heapO(n) 工作)创建一个堆,然后重复@987654324 @ (O(log n) per pop) 从堆中取出并与堆的.front() 进行比较;如果你刚刚弹出的值和前面的值相同,你就有了一个副本,可以立即退出。您还可以使用 priority_queue 适配器将其简化为单个容器,而不是手动使用 std::vector 等上的实用程序函数。

【讨论】:

  • 好的,它们都是,截至目前O(nlog(n)) 算法,但第二个地图版本更加优化,但我可以通过使用std::unordered_set 而不是使其真正成为O(n) 算法std::map 正确吗?
  • @DomFarolino:地图版本已经很早了,所以如果重复很常见,那就是“更优化”了;如果不是,排序可能会因为更简单的代码和提高缓存和内存管理开销而获胜(取决于std::map 的实现)。但是是的,mapset 是每次操作的对数,而 unordered_mapunordered_set 是(平均情况)单个操作的恒定时间,并且可以将整体工作减少到 O(n)
  • 优秀。是的,当我更多地考虑它时,我认为我使用地图的第二个版本仍然是O(nlog(n)),我很困惑为什么它在我的测试中表现得更好。非常感谢
  • @DomFarolino:仅供参考,我刚刚添加了一个关于类似排序方法的注释,它提供了像 map 方法一样的早期行为(因此,如果快速找到重复项,它不会支付对所有数据进行排序的整个O(n log n) 成本)。认为它可能会让您感兴趣(它会让您通过比较两种算法的早期行为来进行更有用的比较)。
  • 太棒了!我不知道std::make_heapO(n),直到刚才我查了一下,真是太好了,谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-09
相关资源
最近更新 更多