这段代码的时间复杂度是O(N^2)

Is the time complexity of this code O(N^2)

这是我对leetcode中一道题的解法。通过我的推论,我得出结论,它的总体时间复杂度为 O(N^2)。但是,我想就此得到确认,这样我就不会在判断算法的 time/space 复杂性时继续犯同样的错误。

哦,问题如下:

Given an input string, reverse the string word by word. e.g. "I am you" == "you am I"

代码如下:-

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]

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

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

感谢帮助!

编辑: 我想问一下包含循环的代码部分的时间复杂度。如果问题是 misleading/confusing,我提前道歉。整个代码块用于说明目的。 :)

时间复杂度为O(n).

StringBuilder 的每次插入 (append(x)) 都在 O(|x|) 中完成,其中 |x|是您要追加的输入字符串的大小。 (平均而言,与建造者的状态无关)。

您的算法迭代整个字符串,并对其中的每个单词使用 String#substring()。由于单词不重叠,这意味着您对每个单词执行一次 substring(),并将其附加到构建器(也是一次)- 为每个单词 x 提供 2|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()方法输出字符串的大小是线性的,而不是原来的(你只复制相关部分,而不是整个字符串)。

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

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();
}

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

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

由于您只遍历和处理输入字符串的每个部分一次(即使您使用的是嵌套循环),复杂度应该在 O(n) 的数量级。为了证明,阿米特已经做了很多工作。

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

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("[\[\]]", "");