【问题标题】:Best way/algorithm to find out if a string consists of only a given set of characters找出字符串是否仅包含给定字符集的最佳方法/算法
【发布时间】:2012-02-25 03:48:02
【问题描述】:

我在一次采访中被问到这个问题, 如果您要找出一个字符串是否仅由一组给定的字符组成。 例如,让字符串集是 {0,1,2,3,4,5,6,7,8,9} 上的所有字符串,即所有“数字”字符串。其中,如果超过 {3,8,5} 的字符串集只是有效的,我如何检查字符串是否只包含有效字符。 说:

Input 8888338385
     Output VALID
Input 887837348234 
Output : Invalid

我建议的方法是蛮力,需要根据无效字符列表检查给定字符串中的每个字符。如果任何一个字符无效,我会跳过检查所有其他字符并显示失败消息。 但是,正如here 所建议的那样,可能会有更好的算法。 请帮忙。

【问题讨论】:

  • 首选的解决方案是 c/c++ 或 java。因此标签。
  • 定义better。内存消耗少?更高的速度?更容易理解和维护?

标签: c++ string algorithm


【解决方案1】:

编辑:感谢 Luc Touraille 对原始算法的巨大改进。

创建一个布尔数组a[10]。对于每个预期的数字e,设置a[e] = true

现在对于输入中的每个数字d,检查a[d] 是否为真。如果不是,则返回 false。如果都成功,则返回 true。

您可以将其推广到具有 256 个元素的数组的所有 ASCII 字符。

如果您的输入字符串长度为 N,您的比较字符串长度为 M,并且字母表中的字母数为 A,那么复杂度为 O(N+M)(扫描您的两个字符串)加上 O(A )(初始化布尔数组)。因此,除非您的字符串长度接近或大于您的字母大小,否则这可能不是最佳选择。

值得指出的是,关于 Niklas Baumstark 的出色 performance comparison,我们的两个解决方案实际上是相同的。此处构造的布尔数组与您在接受 [c1c2 的两态 DFA 中构建的转换表相同 ...]*。我想唯一的区别是 Java 的实现更加通用,需要更多的开销。

【讨论】:

  • +1 好的旧查找数组。通常对于“真正的”编程任务是不切实际的,但通常有助于“什么是最快的方法”来做一些非常特别的事情。
  • 从预期的集合开始不是更快吗?这样,您只需要一个数组,并且一旦发现无效字符就可以停止对输入字符串的迭代。您最多可以进行 M+N 次操作。
  • @Luc 是的,会的;)谢谢。
  • 我实现了一个benchmark to compare this to the regex-based solution。令我惊讶的是,Java 的正则表达式实现实际上似乎很糟糕,它比这个解决方案慢了一个数量级!我本来希望正则表达式引擎也会为这么大的字符类创建一个查找表,但显然它没有。
【解决方案2】:

免责声明:与我的假设相反,Java 似乎 suck 优化了此处使用的正则表达式,这导致代码性能不佳。甚至 Javascript 的正则表达式似乎也比这更快。基准测试还表明,尼克的解决方案非常快。

这绝对是正则表达式的任务。在 Java 中:

public boolean isValidString(String str) {
  return str.matches("[358]*");
}

这应该是O(n) 最坏的情况,并且不能比这更好,因为必须查看每个字符。

如果性能很关键,您可能希望缓存预编译的模式匹配器:

import java.util.regex.Pattern;

public class Matcher {
  private Pattern pattern;

  public Matcher() {
    this.pattern = Pattern.compile("[358]*");
  }

  public isValid(String str) {
    return pattern.matcher(str).matches();
  }
}

【讨论】:

  • 是否也匹配“aa8”或仅匹配“aa”?
  • @awoodland:实际上在许多其他语言(例如 Python、Ruby)中也是如此。通常有一个 search 方法的行为就像你期望的那样,而 matchmatches 将检查整个字符串。
  • 另外,虽然您可能会在生产代码中采用这种方法,但我不建议您在面试中将其作为最终答案...
  • @NiklasBaumstark - 这是一个面试问题。如果他们对解决方案的复杂性感兴趣,那么您的解决方案将是O(M*N),其中 M 是允许的字符集的大小,N 是字符串的长度。如果问题只是在现实世界场景中快速高效地编写代码,那么您的解决方案当然很棒。
  • @WeaselFox:看来你是对的。 Java 的正则表达式实现似乎是very crappy。我觉得这既令人惊讶又有点搞笑,对于像这样的有限状态机,优化真的不是那么难。在这个例子中,甚至 Javascript 的正则表达式也表现得更好......
【解决方案3】:

您可以为允许集中的每个字符使用映射(如果字母表的范围有限),并直接检查您检查的字符串中的每个字符是否在映射中。这样,它只有 O(N),其中 N 是字符串长度,而不是 O(N*M),其中 M 是允许的字符集。如果字母表规模较大,则可以使用其他数据结构来存储允许的字符 - 排序树,例如 O(N)logN 的复杂性。

【讨论】:

    【解决方案4】:

    对于 c 或 c++,你可以这样做:

    const char* haystack = "8888338385";
    const char* filter = "385";
    
    if (strlen(haystack) != strspn(haystack, filter))
    {
      // oops - haystack contains more characters...
    }
    

    存在用于 c++ 的等效 std::string 函数 (std::string::find_first_not_of)

    编辑:我意识到这是作弊,但问题中没有任何东西可以排除这种情况。

    【讨论】:

      【解决方案5】:

      我将首先对输入和无效字母列表进行排序,然后您始终可以确定字符串是否在线性时间内有效

      【讨论】:

      • 对输入进行排序使得输入字符串长度为 O(nlogn),而简单的解决方案是 O(n)。
      • 你的意思是上面的正则表达式解决方案吗?它的复杂度实际上不是 O(n)!您必须将输入的每个字母与无效列表的每个字母进行比较,所以它实际上是 O(m*n)。通过排序你只需要 O(nlogn) + O(mlogm) + O(n+m)
      • @user1180720 正则表达式解决方案可以很容易地实现 O(n),并通过适当的正则表达式实现。一个“严格”的正则表达式(没有捕获)定义了一个 DFA;这实际上是 egrep 的传统实现所使用的。
      • @James:我也希望 Java 能够优化这一点,但它似乎是 very crappy!您知道有关 Sun 实施的任何资源吗?
      • @NiklasBaumstark 不是真的。请注意,捕获会阻止实际使用 DFA。尽管优化版本可以切换实现,如果没有捕获,则使用 DFA。对于像这样的简单表达式,在实践中应该没有回溯,基于 NFA 的实现仍然应该是 O(n)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-20
      • 2011-03-18
      • 1970-01-01
      • 2011-12-06
      • 1970-01-01
      • 2012-12-27
      • 2020-01-19
      相关资源
      最近更新 更多