【问题标题】:How to find distinct indices of elements of one unsorted array in another one?如何在另一个未排序数组中找到一个未排序数组的元素的不同索引?
【发布时间】:2018-02-06 22:08:56
【问题描述】:

有 2 个字符数组。两个数组的大小相同,并且相互杂乱无章。

例如:

char a[] = {'a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b', 'c' };
char b[] = {'a', 'a', 'b', 'b', 'c', 'c', 'b', 'b', 'a', 'a' };

我想在数组a中找到数组b的元素的不同位置(基于1的索引),即:1、3、2、4, 5、10、7、9、6、8,对于这个例子。

我实现了以下蛮力方法 O(n2):

for (int i = 0; i < n; i++)
{
    for (int j = 0; j < n; j++)
    {
        if (b[i] == a[j])
        {
            cout << j + 1 << " ";
            a[j] = '\0';
            break;
        }
    }
}

C++ 中是否有任何方法可以将时间复杂度降低到类似 O(n * log(n))甚至更少?

【问题讨论】:

  • 数组是否只包含 a、b 或 c?
  • 鉴于ab 的元素值,我不明白您是如何得出答案的。
  • 您可以拥有int char_map[256]={0}; char_map[symbol]++; 的地图类型。它会在内存成本上增加一些时间。
  • @ArnavBorborah 数组由 a-z(仅小写)的任意组合组成,没有数字/特殊字符/空格。
  • @dashthird -- C++ 中的索引从 0 开始,而不是 1。为什么您的输出没有反映这一点,至少不会混淆?

标签: c++ arrays time-complexity


【解决方案1】:

它可以减少到 O(n) 时间,但消耗 O(n) 内存。

您应该创建二维数组。它的第一个维度将由所有字符组成(总共 256=2^8 个索引,因为 sizeof(char)=1 字节),第二个维度将超过数组的 n 个元素。

所以,如果你有

char a[n] = ...;
char b[n] = ...;

你应该分配

int c[256][n]; // O(n) memory
int s[256]; // O(1) memory
int e[256]; // O(1) memory

并用零填充它们。您将能够使用 e[i] 作为 a 中代码 i 的字符数计数器。在 c[i][0], c[i][1], ... 中,您可以将代码 i 的字符的实际位置存储在数组 a 中。 p>

第一步是遍历数组a,并且每次

  1. 将字符位置写入 c[a[i]][m] = i,其中 m = e[a[i]]
  2. 增加e[a[i]]

您可以使用数组 s 来存储符号已打印位置的数量(s[j] 是代码为 j 的打印字符数)。第二步是迭代 b 并且每次

  1. 输出 c[b[i]][m],其中 m = s[b[i]]
  2. 增加s[b[i]]

每个步骤都消耗 O(n) 时间,因此总时间复杂度为 O(n)。重要的是要注意,这种复杂性并不是使用随机方法的平均情况(例如您必须考虑命中概率的哈希表)。这种复杂性在最坏的场景中是相同的。

【讨论】:

    【解决方案2】:

    您可以简单地将索引插入到以元素值为键的多映射中,然后迭代另一个数组,找到您需要的第一个索引,然后将其从映射中删除。应该是 O(n log n):

    std::multimap<char, int> charmap;
    for (unsigned i = 0; i < sizeof a; i++) {
        charmap.emplace(a[i], i);
    }
    
    
    for (char c : b) {
        auto it = charmap.find(c);
        std::cout << it->second + 1 << " ";
        charmap.erase(it);
    }
    

    【讨论】:

    • 对于大型数据集,unordered_multimap 应该更快,即使对于第二个循环的单个实例也是如此。大体上,我的意思是至少有数千。
    • @zzxyz 与 multimap 不同,但是,unordered_multimap 不维护元素的插入顺序,因此会导致 一些 可能的索引“混乱”,但是不一定 the 一个混杂,其中b 的第一个元素混杂到元素相等的a 中的第一个索引。
    • 啊,我没想到。人们会认为只有索引会混乱,而不是与它们关联的值。否则,对于大型数据集,我的答案开始看起来相当不错。这是一个奇怪而有趣的问题,我很惊讶我以前没有遇到过。
    【解决方案3】:

    你可以有一个哈希表,在每个桶中,都有一个列表。该列表将包含 b[] 的索引,字符出现在该索引处。

    取 b[] 中的每个元素,比如 b[i],在哈希表中找到索引等于 b[i] 的列表(在您的情况下为 'a' 或 'b' 或 'c' )。如果没有列表,则创建一个值为 i+1 的列表。如果找到列表,则将 i+1 添加到列表末尾

    在您的示例中,插入完成后

    • 'a' 将是列表 1,2,9,10
    • 'b' 将是列表 3,4,7,8
    • 'c' 将是列表 5,6

    在处理条目时,您使用列表中的第一个元素并从列表中删除第一个元素。 在上面的例子中,

    在处理第一个条目“a”后 - 您将给出 1,并从索引“a”处的列表中删除 1。现在

    • 'a' 将是列表 2,9,10
    • 'b' 将是列表 3,4,7,8
    • 'c' 将是列表 5,6

    处理第二个条目“b”后 - 您将给出 3 并从索引“b”处的列表中删除 3。现在

    • 'a' 将是列表 2,9,10
    • 'b' 将是列表 4,7,8
    • 'c' 将是列表 5,6

    这将是 O(N)? (假设你有双向链表)

    【讨论】:

    • 如果 A 和 B 是双向链表而不是数组,你是说复杂度 = O(n) 吗?
    • 没有。我说哈希表插入是 O(1)。要将数组 A 中的 N 个条目添加到哈希表中,需要 O(N)。我们在每个索引处维护一个列表。我刚刚提到该列表是一个双向链表 - 因此我们可以在 O(1) 中添加到末尾并删除头部。
    • 作为初学者,能否推荐一些在线资源来学习STL哈希表的实现? (我赞成你的回答,谢谢)
    【解决方案4】:

    编辑 我更喜欢@user2079303 的答案。相同的概念,更容易实现。

    反向查找的具体实现。您会看到前两个循环是开销。 unordered_map 基本上是另一个答案中建议的“哈希表”。

    auto it 是一个迭代器,其中first 是索引,second 是值(在我们的例子中是queue)。令人困惑的是,作为反向查找,“索引”也是您认为的值(反之亦然,队列中的每个“值”都是a 的索引。

    我使用队列是因为您只想使用每个索引一次(因此pop 相当于将值设置为\0)。

    不过,我认为理解它的最佳方式是在调试器中单步执行。

    #include <unordered_map>
    #include <queue>
    #include <memory>
    #include <iostream>
    
    using namespace std;
    int main()
    {
      char a[] = { 'a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b', 'c' };
      char b[] = { 'a', 'a', 'b', 'b', 'c', 'c', 'b', 'b', 'a', 'a' };
      unordered_map<char, shared_ptr<queue<int>>> reverseIndex;
      //create a queue for each unique char
      for (int i = 0; i < 10; ++i)
      {
        auto it = reverseIndex.find(a[i]);
        if (it == reverseIndex.end())
        {
          reverseIndex.emplace(a[i], make_shared<queue<int>>());
        }
      }
      //put the indexes as *values* into our unordered_map vectors
      for (int i = 0; i < 10; ++i)
      {
        auto it = reverseIndex.find(a[i]);
        it->second->push(i);
      }
    
      //perform the actual work
      for (int i = 0; i < 10; ++i)
      {
        auto it = reverseIndex.find(b[i]);
        cout << it->second->front()+1 << "\n";
      //you'll need to push the value back onto the queue for multiple uses of reverseIndex:  it->second->push(it->second->front());
        it->second->pop();
      }
        return 0;
    }
    

    【讨论】:

    • 你的方法的时间复杂度是多少?
    • O(n)。设置反向索引的开销(只需执行一次)大约为 O(2n)。 (你可以组合循环,但我希望发生的事情是清楚的,反正这并不重要。)
    • @dashthird - unordered_map::find 对于较大的数据集确实会变慢,但幅度不大。我已经用它来索引一组 3000 万个,而且它实际上是即时的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多