【问题标题】:Returning 10 most frequently used words in a document in O(n)在 O(n) 中返回文档中最常用的 10 个单词
【发布时间】:2012-10-25 21:37:19
【问题描述】:

如何设计一种算法,在 O(n) 时间内返回文档中最常用的 10 个单词?如果可以使用额外的空间。

我可以使用 count 解析单词并将其放置在哈希图中。但接下来我必须对值进行排序以获得最常见的值。此外,我还必须有一个映射 btw 值 -> 由于值可能重复,因此无法维护的键。

那么我该如何解决呢?

【问题讨论】:

  • 听起来像家庭作业。你试过什么?
  • 什么是n?正文中的字数?还是文本中的字符数?

标签: java algorithm complexity-theory time-complexity


【解决方案1】:

这是一个简单的算法:

  • 通过文档一次读取一个单词。 O(n)
  • 使用每个单词构建一个哈希表。 O(n)
    • 使用单词作为键。 O(1)
    • 使用您看到这个词的次数作为值。 O(1)
    • (例如,如果将键添加到哈希表中,则值为 1;如果哈希表中已有键,则将其关联值增加 1)O(1)
  • 创建一对大小为 10 的数组(即 String words[10] / int count[10],或使用 ),使用这对来跟踪 10 个最常用的单词及其在下一步。 O(1)
  • 遍历完成的 HashTable 一次:O(n)
    • 如果当前单词的字数高于数组对中的条目,请替换该特定条目并将所有内容向下移动 1 个插槽。 O(1)
  • 输出这对数组。 O(1)

O(n) 运行时间。

O(n) HashTable + 数组的存储

(旁注:您可以将 HashTable 视为字典:一种存储键值对的方法,其中键是唯一的。从技术上讲,HashMap 意味着异步访问,而 HashTable 意味着同步。)

【讨论】:

  • 我很好奇如何“在地图上搜索”新词,以确定您之前是否看过该词不是 O(logN)?既然你必须对每个单词都这样做,那么构建完整的哈希表不是 O(NlogN) 吗?
  • 创建哈希表的复杂度是 O(1) 分摊的(实际上是 O(alpha),但那很麻烦)
  • 是的,这是真的,仅在直接插入时...您只想在以前未见过该词的情况下插入。如果它有,那么你必须在地图中找到它来增加它的计数。这是一个地图 GET,它是 O(logN),而不是 O(1)
  • @BillJames 我认为这就是 Hash 的重点——它将提供 O(1) 插入,因为相同形式的所有单词都会在一个位置发生碰撞。然后你更新那个位置的计数器
  • @AK4749 这假定了一个完美的散列(这对于单词来说可能很容易),并且能够使用散列作为数组的索引。要么你得到一个好的无冲突哈希,要么你得到一小组哈希,通常不会两者兼而有之。
【解决方案2】:

如果使用正确的数据结构,可以在 O(n) 内完成。

考虑一个Node,由两件事组成:

  • 一个计数器(最初设置为 0)。
  • 由 255 个(或任意数量的字符)指向Node 的指针组成的数组。所有指针最初都设置为NULL

创建一个根节点。定义一个“当前”Node 指针,最初将其设置为根节点。 然后遍历文档的所有字符并执行以下操作:

  • 如果下一个字符不是空格 - 从当前节点的数组中选择适当的指针。如果是NULL - 分配它。当前的Node 指针已更新。
  • 如果是空格(或任何单词分隔符)- 增加“当前”Node 的计数器。然后重置“当前”Node 指针指向根节点。

通过这种方式,您可以在 O(n) 中构建一棵树。每个元素(节点和离开)都表示一个特定的单词,以及它的计数器。

然后遍历树以找到具有最大计数器的节点。它也是 O(n),因为树中的元素数量不大于 O(n)。

更新:

最后一步不是强制性的。实际上,最常用的词可能会在字符处理过程中被更新。 以下为伪代码:

struct Node
{
    size_t m_Counter;
    Node* m_ppNext[255];
    Node* m_pPrev;

    Node(Node* pPrev) :m_Counter(0)
    {
        m_pPrev = pPrev;
        memset(m_ppNext, 0, sizeof(m_ppNext));
    }
    ~Node()
    {
        for (int i = 0; i < _countof(m_ppNext) i++)
            if (m_ppNext[i])
                delete m_ppNext[i];
    }

};

Node root(NULL);
Node* pPos = &root;
Node* pBest = &root;
char c;

while (0 != (c = GetNextDocumentCharacter()))
{
    if (c == ' ')
    {
        if (pPos != &root)
        {
            pPos->m_Counter++;

            if (pBest->m_Counter < pPos->m_Counter)
                pBest = pPos;

            pPos = &root;
        }
    } else
    {
        Node*& pNext = pPos->m_ppNext[c - 1];
        if (!pNext)
            pNext = new Node(pPos);
        pPos = pNext;
    }
}

// pBest points to the most common word. Using pBest->m_pPrev we iterate in reverse order through its characters

【讨论】:

    【解决方案3】:

    最快的方法是使用基数树。您可以将单词数存储在基数树的叶子上。保留一个单独的 10 个最常用词及其出现次数的列表,以及一个存储进入该列表所需的阈值的变量。将项目添加到树中时更新此列表。

    【讨论】:

    • 实际上你的答案是正确和精确的,这保证在 O(n) 中工作,不像使用哈希表的得分最高的直接解决方案,它可以理论上在 O 中工作(1),但实际上 - 不保证。
    • 首先创建 Radix Tree(或只是 trie)并计算每个单词的出现次数,创建大小为 10 的 Min Heap,然后扫描 radix 树。
    【解决方案4】:

    我会使用 ArrayList 和 HashTable。

    这是我正在考虑的算法,

    Loop through all the word in the document.
    
    if (HashTable.contains(word) )
        increment count for that word in the HashTable;
    else
        ArrayList.add(word);
        HashTable.add(word);
        word count in HashTable = 1;
    

    遍历整个文档后,

    Loop through ArrayList<word>
        Retrieve the word count for that word from the HashTable;
        Keep a list of the top 10 words;
    

    构造HashTable和ArrayList的运行时间应该是O(n)。 制作前 10 个列表应该是 O(m),其中 m 是唯一单词的数量。 O(n+m) 其中 n>>m --> O(n)

    【讨论】:

      【解决方案5】:

      维护 (word,count) 的映射将是 O(n)。

      地图构建后,遍历键并检索十个最常用的键。

      O(n) + O(n)

      -- 但对这个解决方案并不完全满意,因为需要额外的外部内存。

      【讨论】:

      • “保留地图”并不是真正的解释性。你将如何在 O(n) 中做到这一点,而不必一遍又一遍地搜索地图,每一个都是 logN 搜索?
      • 地图实现查找​​是 O(1),因此在地图中找到一个键。获取它的计数并更新它,对于整个文档来说是 O(n)
      • @SajitKunnumkal 不在地图 O(logN) 中查找?
      • 取决于我们在这里所说的地图。如果它是映射的哈希实现,例如 (HashMap),它的 O(1)
      • 是的。它不一定是 log2,但它是 logX,具体取决于存储桶数。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-28
      • 2021-06-23
      • 2021-11-12
      • 2017-01-15
      • 2014-01-21
      相关资源
      最近更新 更多