【问题标题】:CodeSignal: Execution time limit exceeded with c++CodeSignal:c ++超出了执行时间限制
【发布时间】:2020-12-29 05:58:31
【问题描述】:

我正在尝试首先解决编程问题Duplicate on codesignal。问题是“给定一个数组a,它只包含1a.length 范围内的数字,找到第二次出现的索引最小的第一个重复数字”。

示例:对于a = [2, 1, 3, 5, 3, 2],输出应为firstDuplicate(a) = 3

有 2 个重复项:数字 2 和 3。第二次出现的 3 比第二次出现的 2 的索引更小,所以答案是 3。

使用此代码,我通过了 21/23 测试,但随后它告诉我程序超出了测试 22 的执行时间限制。我该如何让它更快,以便它通过剩余的两个测试?

#include <algorithm>

int firstDuplicate(vector<int> a) {
    vector<int> seen;
    
    for (size_t i = 0; i < a.size(); ++i){
        if (std::find(seen.begin(), seen.end(), a[i]) != seen.end()){
            return a[i];
        }else{
            seen.push_back(a[i]);
        }
    }
    
    if (seen == a){
        return -1;
    }
    
}

【问题讨论】:

标签: c++ execution-time


【解决方案1】:

每当您被问到“查找重复项”、“查找丢失的元素”或“找到应该存在的东西”的问题时,您的第一反应应该是使用哈希表 .在 C++ 中,有用于此类编码练习的 unordered_mapunordered_set 类。 unordered_set 实际上是键到布尔值的映射。

另外,通过引用传递你的向量,而不是值。按值传递会产生复制整个向量的开销。

此外,这种比较最终似乎代价高昂且没有必要。

这可能更接近你想要的:

#include <unordered_set>


int firstDuplicate(const vector<int>& a) {
    std::unordered_set<int> seen;
    
    for (int i : a) {
       auto result_pair = seen.insert(i);
       bool duplicate = (result_pair.second == false);
       if (duplicate) {
          return (i);
       }
    }

    return -1;
   
}

【讨论】:

    【解决方案2】:

    std::find 是容器中第一个和最后一个元素(或直到找到数字)之间的距离的线性时间复杂度,因此最坏情况的复杂度为 O(N),因此您的算法将是 O (N^2)。

    Yyu 应该使用std::map 来存储遇到的数字并返回一个数字,如果在迭代时它已经存在于地图中,则不是将您的数字存储在一个向量中并每次都搜索它。

    std::map<int, int> hash;
    for(const auto &i: a) {
        if(hash[i])
            return i;
        else
            hash[i] = 1;
    }
    

    编辑:如果键的顺序无关紧要,std::unordered_map 的效率会更高,因为与 std::map 的对数插入复杂度相比,插入时间复杂度在平均情况下是恒定的。

    【讨论】:

    • std::unordered_mapstd::map 更有效(哈希表与树插入)。 std::unordered_set 完成与布尔值映射相同的事情。
    • 是的,你是对的,与 std::map 的 log(n) 相比,插入时间是恒定的平均情况。
    【解决方案3】:

    这可能是不必要的优化,但我想我会尝试更好地利用规范。哈希表主要用于从可能的键到实际键的转换相当稀疏的情况——也就是说,只有一小部分可能的键被使用过。例如,如果您的键是长度不超过 20 个字符的字符串,则理论上的最大键数为 25620。有了这么多可能的键,很明显没有实际程序会存储超过很小的百分比,所以哈希表是有意义的。

    然而,在这种情况下,我们被告知输入是:“一个仅包含 1 到 a.length 范围内的数字的数组 a”。因此,即使有一半的数字是重复的,我们也会使用 50% 的可能键。

    在这种情况下,我会使用std::vector&lt;bool&gt;,而不是哈希表,尽管它经常被诽谤,并期望在绝大多数情况下获得更好的性能。

    int firstDuplicate(std::vector<int> const &input) { 
        std::vector<bool> seen(input.size()+1);
    
        for (auto i : input) { 
            if (seen[i])
                return i;
            seen[i] = true;
        }
        return -1;
    }
    

    这里的优势相当简单:至少在典型情况下,std::vector&lt;bool&gt; 使用一种特殊化来存储bools,每个比特仅存储一位。这样,我们为每个输入数量只存储一位,这增加了存储密度,因此我们可以期待缓存的出色使用。特别是,只要缓存中的字节数至少比输入数组中元素数的 1/8th 多一点,我们可以预期所有 seen大部分时间都在缓存中。

    现在不要搞错了:如果你环顾四周,你会发现很多文章指出vector&lt;bool&gt; 存在问题——在某些情况下,这完全正确。有些地方和时间应该避免vector&lt;bool&gt;。但它的任何限制都不适用于我们在这里使用它的方式——而且它确实在存储密度方面提供了一个优势,这可能非常有用,尤其是对于像这样的情况。

    我们还可以编写一些自定义代码来实现位图,该位图将提供比vector&lt;bool&gt; 更快的代码。但是使用vector&lt;bool&gt; 很容易,而编写我们自己的更高效的替换是相当多的额外工作......

    【讨论】:

      猜你喜欢
      • 2021-07-12
      • 2020-08-26
      • 2020-10-24
      • 2012-03-17
      • 1970-01-01
      • 2016-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多