【问题标题】:Is the time complexity of this code O(N^2)这段代码的时间复杂度是 O(N^2)
【发布时间】:2015-08-22 17:44:32
【问题描述】:

这是我针对 leetcode 中的一个问题的解决方案。通过我的推论,我得出结论它具有总体 O(N^2) 时间复杂度。但是,我想对此进行确认,以免在判断算法的时间/空间复杂度时不会继续犯同样的错误。

哦,问题是这样的:

给定一个输入字符串,逐字反转字符串。 例如“我就是你”==“你就是我”

代码如下:-

public String reverseWords(String s) {
        //This solution is in assumption that I am restricted to a one-pass algorithm.
        //This can also be done through a two-pass algorithm -- i.e. split the string and etc.

        if(null == s)
            return "";
        //remove leading and trailing spaces
        s = s.trim();
        int lengthOfString = s.length();
        StringBuilder sb = new StringBuilder();
        //Keeps track of the number of characters that have passed.
        int passedChars = 0;
        int i = lengthOfString-1;
        for(; i >= 0; i--){
            if(s.charAt(i) == ' '){
                //Appends the startOfWord and endOfWord according to passedChars.
                sb.append(s.substring(i+1, (i+1+passedChars))).append(" ");
                //Ignore additional space chars.
                while(s.charAt(i-1) == ' '){
                    i--;
                }
                passedChars = 0;
            }else{
                passedChars++;
            }
        }

        //Handle last reversed word that have been left out.
        sb.append(s.substring(i+1, (i+1+passedChars)));

        //return reversedString;
        return sb.toString();
    }

我认为这是一个 O(N^2) 算法:-

  1. 循环 = O(n)
  2. StringBuilder.append = O(1)
  3. 子字符串方法 = O(n) [从 Java 7 开始]

关于这一点,如果其他人有比这更好的解决方案,请随时分享! :)

我的目标是一次性解决方案,因此选择在循环之前不拆分字符串。

感谢您的帮助!

编辑:我的意思是询问包含循环的代码部分的时间复杂度。如果问题具有误导性/混淆性,我提前道歉。整个代码块是为了澄清目的。 :)

【问题讨论】:

  • 抽象时间复杂度计算完全取决于你算作 1 次操作。
  • 如果您将访问或复制一个字母作为您的基本操作,它可以在线性时间和线性空间中完成。
  • 它需要正确处理Unicode吗?点必须去哪里?您有更多示例的完整规范吗?
  • @biziclop 你好!谢谢你的建议。我已经编辑了我的原始帖子,以澄清我最好奇的代码部分——就时间复杂度而言。你能进一步解释一下你在线性时间/空间中解决问题的意思吗?
  • @Thomas 嗨!没有。您不必担心处理 Unicode。我提供的代码是一个公认的解决方案,所以不用担心它不正确。 :) 我只是对代码“循环”部分的时间复杂度感到好奇。完整的规范/问题可以在 leetcode 的“Reverse Words in a String”标题下找到。

标签: java algorithm performance substring time-complexity


【解决方案1】:

时间复杂度为O(n)

每次插入 (append(x)) 到 StringBuilder 都在 O(|x|) 中完成,其中 |x|是您要附加的输入字符串的大小。 (平均而言,独立于构建器的状态)。

您的算法迭代整个字符串,并为其中的每个单词使用String#substring()。由于单词不重叠,这意味着您为每个单词执行一次substring(),并将其附加到构建器(也一次) - 为每个单词提供2|x|x

总结一下,给你

T(S) = |S| + sum{2|x| for each word x}

但是由于sum{|x| for each word x} <= |S|,这给了你总共:

T(S) = |S| + 2sum{|x| for each word x} = |S| + 2|S| = 3|S|

自从 |S|是输入的大小(n),这是O(n)


注意,重要的部分在 jdk7 中,substring() 方法在输出字符串的大小上是线性的,而不是原始的(你只复制相关部分,而不是整个字符串)。

【讨论】:

  • 嗨!很棒的答案。但是,如果您不介意,我确实有几个问题:- 1) 由于其摊销时间复杂度,附加到 Java 的 StringBuilder 不是在 O(1) 中完成的吗?
  • @cottonman append() 在输入大小的线性时间内完成。它需要一个字符序列,并将其复制到构建器。这个副本是O(|x|),但是,平均而言,它独立于构建器状态的大小(您已经“构建”的)。所以,添加一个很长的字符串不会是O(1),它与添加的字符串的大小是线性的。但是添加恒定数量的字符将是 O(1)。希望它清除。
  • 将 x 个字符附加到 StringBuilder 需要 O(x) 时间,因为您需要复制 x 个字符。快速说明,为了在实现中更清楚,StringBuilder 应该使用大小参数初始化,因为我们知道输出最多与输入一样长。
  • 2|x|来自 (1) substring(),即 O(|x|),和 (2) 来自附加到构建器,也就是 O(|x|)
  • 另请注意,复杂度计算有点简单,因为我们实际上需要将所有乘以某个常数(子字符串是O(|x|),而不是|x|)。这种简化是为了清楚起见,它不会影响答案,只会使其更具可读性,因为我们不需要将所有内容都乘以某个常数。
【解决方案2】:

这是一个替代解决方案,我认为它的性能可能会更好。

public String reverseWords(String s) {
    String[] array = s.split(" ");
    int len = array.length;

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < len; i++) {
        sb.append(" ").append(array[len - i - 1]);
    }

    return sb.toString().trim();
}

【讨论】:

  • 问题不在于如何更有效地做到这一点,而在于建议算法的时间复杂度。这是一个很好的答案,对于不同的问题,恐怕..
  • 引用问题:“在此说明,如果其他人有比这更好的解决方案,请随时分享!:)”
  • 如果不建立原始算法的复杂性,你怎么知道它更好? :)
  • @pnadczuk 嗨!这确实是一个很好的解决方案!快速提问;由于使用了 split() 方法,您是否认为这是一个 2-pass 算法?我的目标是 1-pass 解决方案,因此选择不使用 split() 方法。但是,如果 2-pass 解决方案更有效,我愿意改变我原来的解决方案! :)
  • @biziclop - 这是真的。通过将“”替换为“\\s”可以很容易地对其进行可视化。操作时间几乎翻倍!
【解决方案3】:

Amit 已经给你详细解释了复杂度计算,我想给你一个更简单的版本。

一般来说,如果我们有嵌套循环,我们认为复杂度为 O(N^2)。但情况并非总是如此,因为您必须为输入的每第 n 部分执行 n 次活动。例如,如果您的输入大小为 3,则必须对每个元素执行 3 次操作。然后,您可以说您的算法具有 O(n^2) 复杂度。

由于您只遍历和处理输入字符串的每个部分一次(即使您使用的是嵌套循环),复杂性应该在 O(n) 的数量级上。为了证明,Amit 做得很好。

虽然,我会使用下面的代码来颠倒单词的顺序

String delim = " ";
String [] words = s.split(delim);
int wordCount = words.length; 

for(int i = 0; i < wordCount / 2; i++) {
    String temp = words[i];
    words[i] = words[wordCount - i - 1];
    words[wordCount - i - 1] = temp;
}

String result = Arrays.toString(words).replace(", ", delim).replaceAll("[\\[\\]]", "");

【讨论】:

  • 嗨!你的解释确实帮助我进一步理解了阿米特的推理。所以,感谢您的代码以及您的代码。 :)
猜你喜欢
  • 2018-12-12
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
  • 2015-02-18
  • 2015-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多