【问题标题】:Time complexity of this algorithm该算法的时间复杂度
【发布时间】:2013-02-20 16:11:26
【问题描述】:

我一直在阅读时间复杂度方面的内容,并且已经掌握了基础知识。为了强化这个概念,我看了一下我最近在 SO 上给出的答案。出于某种原因,这个问题现在已经结束,但这不是重点。我不知道我的答案有多复杂,在我得到任何有用的反馈之前问题就被关闭了。

The task 用于查找字符串中的第一个唯一字符。我的回答很简单:

public String firstLonelyChar(String input)
{
    while(input.length() > 0)
    {
        int curLength = input.length();
        String first = String.valueOf(input.charAt(0));
        input = input.replaceAll(first, "");
        if(input.length() == curLength - 1)
            return first;
    }
    return null;
}

Link to an ideone example

我的第一个想法是,因为它会查看每个字符,然后在replaceAll() 期间再次查看每个字符,它会是 O(n^2)。

但是,这让我开始思考它的实际工作方式。对于检查的每个字符,它会删除字符串中该字符的所有实例。所以n 不断缩小。这个因素是如何影响的?那是 O(log n),还是有什么我没看到?

我在问什么:

所写算法的时间复杂度是多少,为什么?

我不是在问什么:

我不是在寻找改进它的建议。我知道可能有更好的方法来做到这一点。我试图更好地理解时间复杂度的概念,而不是找到最佳解决方案。

【问题讨论】:

  • 输入字符串的长度,我想。
  • 我认为这取决于很多事情:1)length() 是否每次都必须计算字符串中的每个字符,或者是单独存储的长度,因此可以在没有线性扫描的情况下返回? 2) 如果length() 是线性的,我们是否至少记住了后续调用的结果? 3) 类使用什么样的内存管理 - 擦除字符是简单的恒定时间操作,还是需要对字符串其余部分进行线性重新复制? 4) 累积擦除何时会导致重新分配和复制?等等……
  • @twalberg:我认为一个安全的假设是replaceAll 必须扫描整个字符串,因为这是“标准”实现,除非另有说明。
  • @recursive Right - replaceAll 当然必须扫描整个字符串以查找匹配项,但问题更多的是实际擦除以及如何处理 - 我可以想到三种同样有效的编写方法replaceAll 从我的脑海中浮现出来,但它们在效率上并不相同(将未擦除的字符复制到临时字符串并复制回来,或者在每次擦除时将字符串尾部向左移动,或者以某种方式简单地标记擦除的字符表示“这里没有字符”)...
  • @twalberg:鉴于它似乎没有进行就地擦除,(input = /* ... */) 你的三种方法中的任何一种是否具有其他线性的运行时复杂性?

标签: algorithm time-complexity


【解决方案1】:

对于字符串aabb...,您将遇到最糟糕的时间复杂度,以此类推,每个字符恰好重复两次。现在这取决于您的字母表的大小,假设是S。让我们还用L 注释初始字符串的长度。因此,对于每个字母,您都必须遍历整个字符串。但是,第一次这样做时,字符串的大小将是 L,第二次是 L-2,依此类推。总体而言,您必须按照L + (L-2) + ... + (L- S*2) 操作的顺序执行,即L*S - 2*S*(S+1),假设L 大于2*S

顺便说一句,如果你的字母表的大小是恒定的,我想它是,你的代码的复杂性是O(L)(虽然有一个很大的常数)。

【讨论】:

    【解决方案2】:

    最坏的情况是O(n^2),其中 n 是输入字符串的长度。想象一下除了最后一个字符之外每个字符都加倍的情况,例如“aabbccddeeffg”。然后有n/2次循环迭代,每次调用replaceAll都要扫描整个剩余的字符串,这也和n成正比。

    编辑:正如 Ivaylo 指出的那样,如果您的字母表的大小是恒定的,那么从技术上讲,它是 O(n),因为您从不多次考虑任何字符。

    【讨论】:

    • 我不认为是N^2 复杂性取决于字母表的大小——他永远不会多次考虑一个字符,所以它更像L * S 的顺序,其中 L是长度,S 是字母表的大小。事实上,如果我们假设 S 是常数,那么复杂度仅为O(N)
    • @IvayloStrandjev:这是一个很好的观点。在整个 unicode 字符集的情况下,N^2 仍然可能是一个更严格的界限。我会在我的回答中做一个说明来反映它。
    【解决方案3】:

    让我们标记:

    m = 单词中唯一字母的数量
    n = 输入长度

    这是复杂度计算:
    主循环最多执行 m 次,因为有 m 个不同的字母,
    .Replaceall 在每个循环中最多检查 O(n) 次比较。

    总数为:O(m*n)

    O(m*n) 循环的一个例子是:input = aabbccdd,

    m=4,n=8

    算法阶段:

    1. input = aabbccdd, complex - 8
    2. input = bbccdd, complex = 6
    3. input = ccdd, complex = 4
    4. input = dd, complex = 2
    

    总计 = 8+6+4+2 = 20 = O(m*n)

    【讨论】:

    • 它正在寻找第一个唯一字符,而不是第一个非唯一字符。您的示例字符串将在“a”处停止。
    • 感谢您的评论,我更改了示例。复杂度计算仍然有效。
    【解决方案4】:

    m 是你的字母表的大小,让n 是你的字符串的长度。更糟糕的情况是在字母之间均匀分布字符串的字符,这意味着字母表中的每个字母都有n / m 个字符,让我们用q 标记这个数量。比如字符串aabbccddeeffgghh16字符在a-h之间的均匀分布,所以这里n=16m=8你每个字母都有q=2个字符。

    现在,您的算法实际上是遍历字母表中的字母(它只使用它们在字符串中出现的顺序),并且对于每次迭代,它都必须遍历字符串的长度 (n) 和将其缩小q (n -= q)。所以在最坏的情况下你做的所有操作都是:

    s = n + n-(1*q) + ... + n-((m-1)*q)
    

    可以看到s是算术级数的前m元素之和:

    s = (n + n-((m-1)*q) * m / 2 =  
        (n + n-((m-1)*(n/m)) * m / 2 ~ n * m / 2
    

    【讨论】:

      猜你喜欢
      • 2021-08-25
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-09
      相关资源
      最近更新 更多