【问题标题】:Is there a way to reverse a string faster than O(n)?有没有办法比 O(n) 更快地反转字符串?
【发布时间】:2020-07-16 03:35:34
【问题描述】:

我有以下代码,使用参数-Xmx<1024M> 运行需要超过 5 秒。

我知道for 循环需要O(q) 时间,reverse()toString() 分别需要O(n) 时间。

有没有办法在少于O(n) 的时间内反转字符串?还是有其他东西减慢了代码速度?欢迎任何帮助!

class Main {
  public static void main(String[] args){
    String s = "a";
    String qa = "200000";
    int q = Integer.parseInt(qa);
    String[] t = new String[q];
    for(int i = 0; i < q; i++) {
      if(i%2==0) {t[i] = "2 1 x";}
      if(i%2==1) {t[i] = "1";}
      if(t[i].toCharArray()[0] == '1') {
        StringBuilder rev = new StringBuilder(s).reverse();
        s = rev.toString();
      } else {
        char letter = t[i].toCharArray()[4];
        if(t[i].toCharArray()[2] == '1') {
          s = letter + s;
        } else {
          s = s + letter;
        }
      }
    }
    System.out.println(s);
  }
}

【问题讨论】:

  • 我不相信这是可能的,因为它需要 n/2 交换
  • 不,不可能
  • 目标只是反转字符串吗?有一个更简单的方法。
  • 为了反转某些东西,您必须主动触摸所有元素以改变它们的位置(假设我们不谈论隐式反转)。按照设计,这受Omega(n) 的约束。想出更快的东西是不可能的(就渐近复杂度而言)。
  • 这是一个很好的问题,但是它需要更新,所以请这样做。如果您想让代码更快并低于 5 秒,请要求优化 Java 代码和算法本身。如果您的问题只是关于实现更好的 O(n),那么就不要谈论时间复杂度了。

标签: java string performance optimization big-o


【解决方案1】:

有没有办法在少于 O(n) 的时间内反转字符串?或者是其他什么东西减慢了代码速度?

没有办法在少于 O(n) 的时间内反转字符串:产生大小为 n 的输出的程序至少需要 o(n) 时间。

您的代码有很多不必要的操作会减慢程序的速度。该程序生成 50000 个字母 x,然后是一个字母 a,然后是另外 50000 个字母 x。这是同一程序的更快(并且更容易理解)的实现。

class Faster {
    public static void main(String[] args) {

        String hundredXs = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

        for (int i = 0; i < 500; i++)
            System.out.print(hundredXs);

        System.out.print("a");

        for (int i = 0; i < 500; i++)
            System.out.print(hundredXs);

        System.out.println();
    }
}

【讨论】:

    【解决方案2】:

    不管它应该做什么(我不知道),我发现了以下问题:

    • 在每次迭代中多次实例化StringBuilder
    • 使用+ 运算符连接字符串。
    • 重复使用Sring::toCharArray(参见第二个解决方案)

    您将直接使用StringBuilder 的一个实例获得更快的结果:

    String s = "a";
    String qa = "200000";
    int q = Integer.parseInt(qa);
    String[] t = new String[q];
    StringBuilder sb = new StringBuilder(s);       // Instantiate before the loop
    for (int i = 0; i < q; i++) {
        if(i%2==0) {t[i] = "2 1 x";}
        if(i%2==1) {t[i] = "1";}
        if(t[i].toCharArray()[0] == '1') {
            sb.reverse();                          // all you did here is just reversing 's'
        } else {
            char letter = t[i].toCharArray()[4];
            if(t[i].toCharArray()[2] == '1') {
                sb.insert(0, letter);              // prepend a letter
            } else {
                sb.append(letter);                 // append a letter
            }
        }
    }
    

    另一件事是您多次定义一个字符串,例如t[i] = "2 1 x";,然后与t[i].toCharArray()[0] 进行比较。预定义这些不可变值并使用 char[][] 也应该有所帮助:

    String s = "a";
    String qa = "200000";
    int q = Integer.parseInt(qa);
    char[][] t = new char[q][];                    // char[][] instead of String[]
    char[] char21x = new char[]{'2', '1', 'x'};    // predefined array
    char[] char1 = new char[]{'1'};                // another predefined array
    StringBuilder sb = new StringBuilder(s);       // Instantiate before the loop
    for (int i = 0; i < q; i++) {
        if(i%2==0) {t[i] = char21x;}     // first reuse
        if(i%2==1) {t[i] = char1;}       // second reuse
        if(t[i][0] == '1') {             // instead of String::toCharArray, mind the indices
            sb.reverse();                // all you did here is just reversing 's'
        } else {
            char letter = t[i][2];       // instead of String::toCharArray, mind the indices
            if(t[i][1] == '1') {
                sb.insert(0, letter);    // prepend a letter
            } else {
                sb.append(letter);       // append a letter
            }
        }
    }
    

    编辑:我已经在我的笔记本电脑上使用System.currentTimeMillis() 的差异以最简单的方式测试了解决方案:

    • 原解决方案:7.6586.8997.046
    • 第二个解决方案:3.2883.6913.158
    • 第三种解决方案:2.7172.9662.717

    结论:我认为在计算复杂度方面没有办法改进算法本身,但是,使用正确的方法来处理字符串有助于将时间复杂度降低 2-3 倍(在我的案例)。

    一般建议:您可以在循环之前实例化和定义什么,在循环之前执行。

    【讨论】:

    • 在我的笔记本电脑上,大约需要 2 秒,而原始解决方案需要 6 秒
    • OP 也在转换成一个字符串,然后反馈给 StringBuilder。
    • @Zabuza:是的,我一直在考虑这个问题,但我从来没有这样做过。看来我必须填补我的知识空白:D
    • @WJS:如果你的意思是t[i].toCharArray()-alike 电话,我已经将它包含在我的答案中。
    猜你喜欢
    • 1970-01-01
    • 2020-07-19
    • 2023-04-01
    • 2014-06-02
    • 1970-01-01
    • 2019-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多