删除一个字符后,检查字符串是否为回文结构。我的工作解决方案太慢了

时间:2016-05-23 21:51:58

标签: java string algorithm palindrome

我似乎找不到适当的练习方法。练习要求创建一个方法,如果字符串可以通过删除一个字符而成为回文,则返回true。我有一个解决方案,但无法测试大型(100,000个字符)字符串,因为它超过了1秒的时间限制。有人能指出我正确的方向吗?

我意识到我的方法是蛮力的,我确信有更好的解决方法。我假设我的问题在于迭代。

public class Main {

    public static boolean makePalindrome(String mjono) {

        StringBuilder sb = new StringBuilder(mjono);


        for (int i = 0; i < mjono.length(); i++) {
            sb.deleteCharAt(i);

            if(isPalindrome(sb.toString())){
                return true;
            } else {
                sb.insert(i, mjono.charAt(i));
            }
        }
        return false;
    }

    private static boolean isPalindrome(String string) {
        return string.equals(new StringBuilder(string).reverse().toString());
    }

    public static void main(String[] args) {
        System.out.println(makePalindrome("ABCBXA"));
        System.out.println(makePalindrome("ABCBAX"));
        System.out.println(makePalindrome("ABCXBA"));
        System.out.println(makePalindrome("ABCDE"));
        System.out.println(makePalindrome("BAAAAC"));
    }
}

这些测试失败了:

@Test(timeout=1000)
public void suuri2() {
    int n = 100000;
    char[] t = new char[n];
    for (int i = 0; i < n; i++) t[i] = 'A';
    t[12345] = 'B';
    testaaSuuri(new String(t), true);
}

@Test(timeout=1000)
public void suuri3() {
    int n = 100000;
    char[] t = new char[n];
    for (int i = 0; i < n; i++) t[i] = 'A';
    t[12345] = 'B';
    t[54321] = 'C';
    testaaSuuri(new String(t), false);
}

提前致谢。

4 个答案:

答案 0 :(得分:5)

嗯,当然,通过尝试删除一个字符的每种可能性,O(n ^ 2)中运行的天真解决方案。

但我们当然可以做得更好:
我们可以递归地定义一个回文:

palindrome = x.palindrome.x | x | x.x , where x is an arbitrary token

那么这对我们有什么帮助?非常简单:我们可以导出一个规则,允许在O(n)中检查字符串是否是回文。

回文由char c组成,后跟一个必须为空或回文的字符串,如果长度超过1个字符,则后跟另一个c。如果长度为1,则自动回文。

因此,最后一个字符必须等于第一个,第二个字符必须等于第二个字符,依此类推。所以基本上:

boolean isPalindrome(String s){
    for(int i = 0 ; i < s.length() / 2 ; i++)
        if(s.charAt(i) != s.charAt(s.length() - i - 1))
            return false;

    return true;
}

我们必须稍微改变这个规则,因为一旦我们删除了一个字符。这引入了将整个问题分为两部分,正如我们从定义中看到的那样:

palindrome_1 = s.x.palindrome.reverse(s) | s.palindrome.x.reverse(s) | palindrome

正如我们可以很容易看到的,这包含原始的回文定义,但此外还允许引入一个额外的char x

static boolean isPalindrome_1(String s){
    for(int i = 0 ; i < s.length() / 2 ; i++)
        if(s.charAt(i) != s.charAt(s.length() - i - 1))
            return isPalindrome(s , i + 1 , s.length() - i - 1) ||
                    isPalindrome(s , i , s.length() - i - 2);

     return true;
}

static boolean isPalindrome(String s , int lower , int upper){
    while(lower < upper){
        if(s.charAt(lower) != s.charAt(upper))
            return false;

        lower++;
        upper--;
    }

    return true;
}

解释/或至少试图解释这一点:
这段代码:

if(s.charAt(i) != s.charAt(s.length() - i - 1))
    return isPalindrome(s , i + 1 , s.length() - i - 1) ||
        isPalindrome(s , i , s.length() - i - 2);

如果palindrome的定义不适用于我们的输入字符串,则是必需的。在这种情况下,我们必须检查两种可能性,代码是如何构建的:

s.x.palindrome.reverse(s)
s.palindrome.x.reverse(s)

如果palindrome的定义不适用,我们已达到一定程度,我们是否必须省略剩余字符串(x.palindrome)开头的字符或结束剩余的字符串(palindrome.x),如果其余字符与回文的定义匹配,请参阅。这是通过使用两个不同的子串调用isPalindrome(...)来完成的,这些子串在剩余字符串的开头或结尾处被一个字符剪切。

此代码如何工作的几个示例:

A B C D E F E D C B A
|                   | portion that runs inside isPalindrome_1

A B D E F E D C B A
| |             | |  portion that can be checked inside isPalindrome_1
    |       |        isPalindrome(s , i , s.length() - i - 2)
      |       |      isPalindrome(s , i + 1 , s.length() - i - 1)

正如我们在第二个例子中所看到的,代码搜索了第一对不相等的字符。此时,我们有两个子串进一步搜索,每个子串都在字符串的开头或结尾省略一个字符。

效率:
此代码就地运行 - 从未创建输入字符串的任何副本。运行时为O(n)(确切地说O(2 * n))。建立更快的解决方案是不可能的 - 至少在我们获得量子计算机之前;)

答案 1 :(得分:2)

提示1:由于这是一项练习,因此发布解决方案是不合适的。 (这会削弱自己进行锻炼的学习经历。)

提示2:对于O(N)字符String或StringBuilder,以下操作都是N

  1. 在StringBuilder中添加或删除字符
  2. 从现有的StringBuilder创建新的StringBuilder
  3. 扭转StringBuilder
  4. 从StringBuilder(toString()
  5. 创建字符串
  6. 比较两个相等或几乎相等的&#34;字符串对象。
  7. (在大多数情况下,您复制或比较N个字符。对于插入和删除,您复制平均0.5 N个字符,假设缓冲区不需要增长,但仍然是{{1对于O(N) ......这很复杂,但最糟糕的情况显然是equals。)

    因此,用于大字符串的快速回文测试仪需要避免这些操作。

    提示3:您可以将字符串视为字符数组,方法是将其转换为O(N)或使用char[]

    提示4:您不必从字符串中物理删除字符。你可以让你的算法假装它不在那里。

答案 2 :(得分:0)

我们可以使用LCS(最长公共子序列)解决此问题。 LCS告诉我们两个字符串中最长子序列的长度。

boolean isPalindromAfterRemovingOneChar(String s) {
    int lengthOfLCS = lcs(s, s.reverse(), s.length());
    return (s.length()- lengthOfLCS) == 1;
} 

答案 3 :(得分:-1)

只需比较上半场到下半场。不要浪费时间扭转整个字符串。

private boolean isPalindrome(String string) {
    char[] values = string.toCharArray();

    for (int i = 0; i < values.length / 2; i++) {
       if (values[i] != values[values.length - 1 - i])
           return false;
    }

    return true;
}