JS:编写一个函数,遍历字符串列表并返回列表中出现频率最高的前10个字符串

我正在尝试编写一个函数,它遍历字符串列表并返回列表中前 10 个最常用的字符串。我正在尝试为这个问题提出多种解决方案

这是我的第一个解决方案

const list = [
    "this",
    "is",
    "a",
    "test",
    "which",
    "word",
    "wins",
    "top",
    "i",
    "don't",
    "know",
    "off",
    "hand",
    "do",
    "you",
    "this",
    "a",
    "a",
    "this",
    "test",
    "a",
    "a",
    "do",
    "hand",
    "hand",
    "a",
    "whatever",
    "what",
    "do",
    "do"
  ];

function fn1(strArr) {
    const map = new Map()
    for(const str of strArr) {
        if(map.has(str)) {
            map.set(str, map.get(str) + 1)
        } else {
            map.set(str, 1)
        }
    }
    const sortedMap =[...map.entries()].sort(([_,a], [__,b]) => a < b ? 1 : -1)
    return sortedMap.slice(0 , 10).map(([str]) => str)
}

但我似乎找不到这个问题的任何其他解决方案。任何人都可以提出替代建议吗?

另外,需要注意的一件事是列表可能非常大,可能包含 100 万个字符串。所以我们需要尽量减少运行时的复杂度

回答

我相信以下解决方案在实践中是最快的:

  1. n正如您已经完成的那样,将字符串映射到它们的频率。O(n)
  2. 将映射转换为具有字符串/频率对的数组。 O(n)
  3. 使用 Floyd 的方法根据频率将数组转换为 Max-heap(即,通过对从?n/2? - 1下到 0 的所有索引调用 max-heapify )。O(n)
  4. 提取顶部元素k时间。(在你的情况下,k=10。)O(k log n)

(我在这里不提供任何代码,因为它基本上包括调用二进制堆库(请参阅此处以获得高度优化的实现)。

分析

渐近复杂度O(n + k log n)低于O(n log k)Chaos Monkey 提供的解决方案。对于小k(例如 10),可能不会有任何显着差异。较大的差异变得更加明显k(只要k ? n; 对于更大的k,请参阅下面的“替代方案”)。还要注意,第1步和第2步的常数因子是1,第3步的常数因子平均也很小(1.8814),所以总的常数因子小于4。

选择

有一个解决方案可以O(n)平均解决问题,也有一个小的常数因子,这对于更大的k(即k接近时n/2)效率更高。缺点是(极不可能的)最坏情况的复杂性是O(n²)

  1. n正如您已经完成的那样,将字符串映射到它们的频率。O(n)
  2. 将映射转换为具有字符串/频率对的数组。 O(n)
  3. 应用Quickselect(与 Quicksort 完全一样,但只是在分区的一侧递归)以找到k最大频率。左边的所有元素都更大,因此结果是kQuickselected 数组的第一个元素。O(n)平均,O(n²)最坏情况。

可以O(n)使用中位数枢轴选择的中位数来实现具有保证复杂性的 Quickselect 的变体,但这在实践中并不是那么好,因为常数因子非常高。但从学术的角度来看,这将是渐近最优解。

(这是一个用于 Quickselect 的 JavaScript 库,尽管快速浏览,该实现看起来并不适合这种情况:一个好的实现应该执行Dijkstra 风格的 3 路分区。)

基准

一个带有n = 10^6, 的快速而肮脏的基准测试,k = 10仅测量第 2 步之后的运行时间(因为第 1/2 步在所有 5 个方法之间共享):

Average time for Sort: 7.5 ms
Average time for ChaosMonkey: 10.25 ms
Average time for CountingSort: 5.25 ms
Average time for Mo B. Max-Heap: 4 ms
Average time for Mo B. Alternative (Quickselect): 3.25 ms

https://dotnetfiddle.net/oHRMsp

我的结论是,对于给定的参数,不同方法之间没有太大区别。为简单起见,我只会坚持排序方法,它也适用于nk

(很多警告:它是用 C# 编写的(草率的),而不是 JavaScript;它没有经过正确性测试;个别方法没有优化;运行时也可能取决于频率的分布,实现的 Quickselect 是幼稚的,因为它没有优化对于(此处)许多频率相等的常见情况等...)

关于空间的最后说明

所有基准方法都使用额外的O(n)空间,因为它们首先创建频率图(计数排序方法O(n)在频率图顶部使用额外的最坏情况空间进行计数)。可以只用O(1)额外的空间来解决这个问题,但代价是时间复杂度为O(kn²)


以上是JS:编写一个函数,遍历字符串列表并返回列表中出现频率最高的前10个字符串的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>