【问题标题】:Why is StringBuilder.reverse faster than appending to linkedlists为什么 StringBuilder.reverse 比附加到链表快
【发布时间】:2021-10-10 21:03:15
【问题描述】:

Leetcode 问题 125. 有效回文:

Given a string s, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.

Example 1:

Input: s = "A man, a plan, a canal: Panama"
Output: true
Explanation: "amanaplanacanalpanama" is a palindrome.

我将每个字符附加到两个链表中,一个向前一个向后,并比较它们。但是,我没有超过时间限制。 Leetcode 的解决方案使用了 StringBuilder 并将其反转。听说StringBuilder的实现类似于链表。我不知道为什么我的代码比解决方案慢得多。我将不胜感激有关此主题的任何反馈或见解。提前谢谢你。

我的代码:

class Solution {
    public boolean isPalindrome(String s) {
        LinkedList<Character> forward = new LinkedList<Character>();
        LinkedList<Character> backward = new LinkedList<Character>();
        for(int i = 0 ; i < s.length() ; i++){
            char ch = s.charAt(i);
            if(Character.isLetterOrDigit(ch)){
                if(Character.isLetter(ch)) ch = Character.toLowerCase(ch);
                forward.addLast(ch);
                backward.addFirst(ch);
            }
        }
        for(int i = 0 ; i < forward.size() ; i++){
            if(forward.get(i) != backward.get(i)) return false;
        }
        return true;
    }
}

Leetcode 解决方案:

class Solution {
  public boolean isPalindrome(String s) {
    StringBuilder builder = new StringBuilder();

    for (char ch : s.toCharArray()) {
      if (Character.isLetterOrDigit(ch)) {
        builder.append(Character.toLowerCase(ch));
      }
    }

    String filteredString = builder.toString();
    String reversedString = builder.reverse().toString();

    return filteredString.equals(reversedString);
  }
}

【问题讨论】:

  • 可能是因为 linkedList.get(i) 是一个 O(n) 操作(它必须遍历列表才能找到元素 i)。这会导致您的比较循环实际上是 O(n^2)。
  • 听说StringBuilder的实现类似于链表。我我找到的在线源代码(JDK 8)显示实现是一个char数组。
  • "听说StringBuilder的实现类似于链表。"不是。
  • 我猜道德是,不要相信你在互联网上听到的一切:-)

标签: java linked-list stringbuilder palindrome


【解决方案1】:

如果您查看reverse() 方法在AbstractStringBuilder 中是如何实现的,您会发现它使用数组来存储字符。这是StringBuilder 和您的解决方案之间的主要区别。 forward.get(i)backward.get(i) 的复杂度为 O(n),而 value[j] 的复杂度为 O(1)。

Java 8 实现:

public AbstractStringBuilder reverse() {
    boolean hasSurrogates = false;
    int n = count - 1;
    for (int j = (n-1) >> 1; j >= 0; j--) {
        int k = n - j;
        char cj = value[j];
        char ck = value[k];
        value[j] = ck;
        value[k] = cj;
        if (Character.isSurrogate(cj) ||
            Character.isSurrogate(ck)) {
            hasSurrogates = true;
        }
    }
    if (hasSurrogates) {
        reverseAllValidSurrogatePairs();
    }
    return this;
}

【讨论】:

    【解决方案2】:

    实际上,Leetcode 的解决方案似乎并不是最好的,并且根本不需要使用StringBuilder::reverse 方法来检测回文,因为可以检查从字符串的开头和结尾到它的字符居中,一旦找到不匹配的对,该字符串就不是回文。

    StringBuilder 转换为直接字符串和反向字符串也是多余的。

    class Solution {
      public boolean isPalindrome(String s) {
          StringBuilder builder = new StringBuilder(s.length());
    
          for (char ch : s.toCharArray()) {
              if (Character.isLetterOrDigit(ch)) {
                  builder.append(Character.toLowerCase(ch));
              }
          }
          for (int i = 0, n = builder.length(), m = n / 2; i < m; i++) {
              if (builder.charAt(i) != builder.charAt(n - i - 1)) {
                  return false;
              }
          }
          return true;
      }
    }
    

    使用 Stream API 和正则表达式清除非字母和非数字字符(Unicode 格式)的类似解决方案:

    public static boolean isPalindrome(String s) {
        String str = s.replaceAll("[^\\p{L}\\p{N}]", "").toLowerCase();
        final int n = str.length();
        return IntStream.range(0, n / 2)
                    .allMatch(i -> str.charAt(i) == str.charAt(n - 1 - i));
    }    
    

    【讨论】:

      猜你喜欢
      • 2015-07-26
      • 2011-07-23
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 2020-11-17
      • 2019-11-02
      • 2015-06-29
      • 2010-11-22
      相关资源
      最近更新 更多