【问题标题】:How to find better algorithm for my prefix matcher algorithm如何为我的前缀匹配器算法找到更好的算法
【发布时间】:2020-02-17 01:02:49
【问题描述】:

我正在解决在线问题,任务是这样的:

有两个数组:数字前缀

  • 数组numbers包含数字:“+432112345”、“+9990”、“+4450505”
  • 数组prefixes包含前缀:“+4321”、“+43211”、“+7700”、“+4452”、“+4”

查找每个号码的最长前缀。如果没有找到数字前缀,则匹配空字符串。

例如:

  • “+432112345”匹配最长前缀“+43211”(不是+4321,因为43211更长)。
  • “+9990”不匹配任何内容,所以空字符串“”。
  • “+4450505”与“+4”匹配(“+4452”不匹配,因为 2)。

我想出了最直接的解决方案,我用每个前缀遍历每个数字。所以每次新号码,我都会检查前缀,如果某个前缀比上一个长,我会改变。

Map<String, String> numsAndPrefixes = new HashMap<>();

for (String number : A) {
    for (String prefix : B) {
        if (number.contains(prefix)) {
            // if map already contains this number, check for prefixes. 
            // if longer exists, switch longer one
            if (numsAndPrefixes.containsKey(number)) {
                int prefixLength = prefix.length();
                int currentLen = numsAndPrefixes.get(number).length();
                if (prefixLength > currentLen) {
                    numsAndPrefixes.put(number, prefix);
                }
            } else {
                numsAndPrefixes.put(number, prefix);
            }
        } else if (!number.contains(prefix) && !numsAndPrefixes.containsKey(number)){
            numsAndPrefixes.put(number, "");
        }
    }
}

所以它将有两个 for 循环。我看到每次我一遍又一遍地做同样的工作,例如检查前缀。它有效,但速度很慢。问题是我想不出更好的东西。

有人能解释一下他们将如何找到更好的算法吗?

更一般地说,如果您有一些可行的解决方案并试图找到更好的解决方案,您将如何进行?我还缺少什么知识?

【问题讨论】:

  • 您可以使用 Trie 或 Suffix 树数据结构来获得更优化的解决方案,但它仍然取决于时间限制等类似的东西,如果您的解决方案运行得足够快,可以满足您的目的,那么这些数据结构就太过分了。跨度>
  • 您确实意识到number.contains(prefix)O(max_prefix.length) 并且您检查了numbers.length*prefix.length 次,您的实际复杂性最终以O(n*p*max_prefix.length) 结束,其中nnumbers 的数量而pprefixes 的编号。
  • 不,我没有意识到这一点。感谢您指出这一点。将检查contains() 功能。
  • 其实contains就是O(max_number.length)

标签: java algorithm data-structures


【解决方案1】:

我将使用TreeSetfloor(E e) 方法来实现这一点。

String[] numbers = { "+432112345", "+9990", "+4450505" };
String[] prefixes = { "+4321", "+43211", "+7700", "+4452", "+4" };

TreeSet<String> prefixSet = new TreeSet<>(Arrays.asList(prefixes));
for (String number : numbers) {
    String prefix = prefixSet.floor(number);
    while (prefix != null && ! number.startsWith(prefix))
        prefix = prefixSet.floor(prefix.substring(0, prefix.length() - 1));
    if (prefix == null)
        prefix = "";
    System.out.println(number + " -> " + prefix);
}

输出

+432112345 -> +43211
+9990 -> 
+4450505 -> +4

【讨论】:

  • 这很好,但我看不出你的解决方案为什么比我的更快。您仍然使用前缀循环每个数字,我看到仍然是 O(n**2)?不?您能否解释一下将数组放入 TreeSet 的成本是多少?当其他一些数据结构更好时可以这样做吗?
  • @AverageJoe9000 您的代码是numbersprefixes 的完整笛卡尔连接,因此O(n*m)。这个解决方案是O(n*log m)。平均而言,它可能只需要在 prefixSet 中查找每个电话号码大约 3 次,而不是扫描完整的 prefix 数组。当然,构建Set 需要一次性O(m*log m) 启动成本,所以如果你只有几个数字或前缀,可能不值得,但是如果前缀列表很大,如果你正在做所有的国际前缀,并且你有很多电话号码要检查,那么性能提升是巨大的。
  • 感谢您的解释。您能否简要描述一下您如何处理这种解决方案?我想弄清楚我在哪里缺乏一些知识。
  • @AverageJoe9000 很难说。了解各种 Java API 类的功能。您不必记住细节,只需记住高级功能,例如在这种情况下,查找相邻值的能力,这是TreeMapTreeSet 的一个特性,以及排序数组,您可以使用Arrays.binarySearch() 在排序数组中查找位置。因此,在非常高的层次上,可以为已排序的集合找到相邻值。
【解决方案2】:

你需要的数据结构是trie

  • 在 trie 中添加所有前缀
  • 对于numbers 中的每个字符串S
    • 从trie的根开始
    • 对于S 中的每个字符:
      • 如果当前节点存在与当前角色相关联的链接,则通过此链接前往下一个节点
      • 如果没有链接,那么你到达了最长的前缀——存储在当前节点中的前缀是S的答案

此算法适用于O(length(prefixes) + length(numbers))

【讨论】:

  • 我会这样做。
【解决方案3】:

您正在使用.contains()。你应该使用.startsWith()。它快了很多

然后在您的else if 中检查您已经在if 中检查的内容。

这只是关于如何改进算法的一种方法

对前缀排序:

+43211 +4321 +4452 +4 +7700

这有什么好处?好吧,它总是会首先找到最长的前缀。您可以退出循环,而不必寻找更长的前缀。

Arrays.sort(prefixes, new Comparator<String>() {
@Override
public int compare​(String o1, String o2) {
    return o1.startsWith(o2) ? 1 : o1.compareTo(o2);
}
});

Map<String, String> numsAndPrefixes = new HashMap<>();

for (String number: numbers) {
    numsAndPrefixes.put(number, "");
    for (String prefix: prefixes) {
        if (number.startsWith(prefix, 1)) {
            numsAndPrefixes.put(number, prefix);
            break;
        }
    }
}

但是如果您的号码以 +1 开头并且没有前缀,它将继续检查所有带有 +2 +3 +4 的前缀 ...显然不匹配。 (问题一)

此外,如果您的号码以+9 开头,则将很晚才能找到前缀。 (问题2)

如何解决这个问题?好吧,您可以保存 +1 开始、+2 开始的索引,...:

在我们的前缀列表中:

0      1     2     3  4  5   (index)
+1233  +123  +2233 +2 +3 +4

+2 从索引 [2] 开始,+3 从索引 [4] 开始。因此,当您想知道以 +2 开头的数字的前缀时,您只需检查元素 [2] 和 [3]。这将解决问题 1 和 2。

还可以存储更多数字的索引(例如 +13 开始的位置)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-02
    • 2011-12-18
    • 1970-01-01
    • 2022-01-10
    • 2012-07-03
    • 2011-05-21
    • 1970-01-01
    • 2012-04-21
    相关资源
    最近更新 更多