确定给定的字符串是否为k-palintrome

时间:2014-01-02 21:45:24

标签: java algorithm palindrome

我正在尝试解决以下面试实践问题:

  

k-palindrome是一条在最多移除时变成回文的字符串           k个字符。

     

给定一个字符串S和一个整数K,如果S是k-palindrome,则打印“YES”;           否则打印“否”。

     

约束:

     

S最多包含20,000个字符           0 <= k <= 30

     

示例测试用例:

Input - abxa 1 
Output - YES 

Input - abdxa 1 
Output - NO

我决定采用长度为s.length - k或更长的所有可能的字符串组合,即“abc”和k = 1 - &gt; “ab”“bc”“ac”“abc”并检查它们是否是回文。到目前为止,我有以下代码,但似乎无法找出在一般情况下生成所有这些字符串组合的正确方法:

public static void isKPalindrome(String s, int k) {
  // Generate all string combinations and call isPalindrome on them,
  //   printing "YES" at first true 
}

private static boolean isPalindrome(String s) {
    char[] c = s.toCharArray()
    int slow = 0;
    int fast = 0;
    Stack<Character> stack = new Stack<>();
    while (fast < c.length) {
        stack.push(c[slow]);
        slow += 1;
        fast += 2;
    }
    if (c.length % 2 == 1) {
        stack.pop();
    }
    while (!stack.isEmpty()) {
        if (stack.pop() != c[slow++]) {
            return false;
        }
    }
    return true;
}

任何人都可以找到实现这一目标的方法,或者可能展示更好的方法吗?

7 个答案:

答案 0 :(得分:4)

我认为有更好的方法

package se.wederbrand.stackoverflow;

public class KPalindrome {
    public static void main(String[] args) {
        KPalindrome kPalindrome = new KPalindrome();
        String s = args[0];
        int k = Integer.parseInt(args[1]);
        if (kPalindrome.testIt(s, k)) {
            System.out.println("YES");
        }
        else {
            System.out.println("NO");
        }
    }

    boolean testIt(String s, int k) {
        if (s.length() <= 1) {
            return true;
        }

        while (s.charAt(0) == s.charAt(s.length()-1)) {
            s = s.substring(1, s.length()-1);

            if (s.length() <= 1) {
                return true;
            }
        }

        if (k == 0) {
            return false;
        }

        // Try to remove the first or last character
        return testIt(s.substring(0, s.length() - 1), k - 1) || testIt(s.substring(1, s.length()), k - 1);
    }
}

由于K最大为30,因此很可能很快就会使字符串失效,甚至不检查字符串的中间部分。

我用两个提供的测试用例测试了这个,以及一个20k字符长的字符串,只有“ab”10k次,k = 30;

所有测试都很快并返回正确的结果。

答案 1 :(得分:2)

这可以使用Edit distance动态编程算法来解决。编辑距离DP算法用于查找将源字符串转换为目标字符串所需的最小操作。操作可以是添加或删除字符。

K-palindrome问题可以使用编辑距离算法通过检查将输入字符串转换为反向所需的最小操作来解决。

让editDistance(source,destination)成为获取源字符串和目标字符串的函数,并返回将源字符串转换为目标字符串所需的最小操作数。

如果editDistance(S,反向(S))&lt; = 2 * K

,则字符串S为K-palindrome

这是因为我们可以通过删除最近的K个字母然后在不同的位置插入相同的K个字母来将给定的字符串S转换为反向字符串。

通过一个例子,这将更加清晰。

设S = madtam,K = 1。

首先我们必须删除字符&#39;在S的索引3(基于0的索引)。

现在中间字符串是女士。然后我们必须插入字符&#39; t&#39;在中间字符串的索引2处得到&#34; matdam&#34;这与字符串s相反。

如果仔细观察,你会知道中间字符串&#34;女士&#34;是通过删除k = 1个字符获得的回文结构。

答案 2 :(得分:1)

我找到了最长字符串的长度,这样在删除字符&gt; = k之后,我们将会有一个回文。我在这里使用了动态编程。我认为的回文不必是连续的。它像abscba一样,最长的回文长度为4.

所以现在可以进一步使用,这样每当k&gt; =(len-len of laongest palindrome)时,结果为true,否则为false。

 public static int longestPalindrome(String s){
        int len = s.length();
        int[][] cal = new int[len][len];
        for(int i=0;i<len;i++){
            cal[i][i] = 1; //considering strings of length = 1
        }
        for(int i=0;i<len-1;i++){
            //considering strings of length = 2
            if (s.charAt(i) == s.charAt(i+1)){
                cal[i][i+1] = 2;
            }else{
                cal[i][i+1] = 0;
            }
        }

        for(int p = len-1; p>=0; p--){
            for(int q=p+2; q<len; q++){
                if (s.charAt(p)==s.charAt(q)){
                    cal[p][q] = 2 + cal[p+1][q-1];
                }else{
                    cal[p][q] = max(cal[p+1][q], cal[p][q-1]);
                }
            }
        }
        return cal[0][len-1];
    }

答案 3 :(得分:0)

感谢Andreas,这个算法就像一个魅力。在这里,我的实施对任何好奇的人。略有不同,但基本上是相同的逻辑:

public static boolean kPalindrome(String s, int k) {
    if (s.length() <= 1) {
        return true;
    }
    char[] c = s.toCharArray();
    if (c[0] != c[c.length - 1]) {
        if (k <= 0) {
            return false;
        } else {
            char[] minusFirst = new char[c.length - 1];
            System.arraycopy(c, 1, minusFirst, 0, c.length - 1);
            char[] minusLast = new char[c.length - 1];
            System.arraycopy(c, 0, minusLast, 0, c.length - 1);
            return kPalindrome(String.valueOf(minusFirst), k - 1)
                   || kPalindrome(String.valueOf(minusLast), k - 1);
        }
    } else {
        char[] minusFirstLast = new char[c.length - 2];
        System.arraycopy(c, 1, minusFirstLast, 0, c.length - 2);
        return kPalindrome(String.valueOf(minusFirstLast), k);
    }
}

答案 4 :(得分:0)

使用着名的最长公共子序列(LCS)方法可以解决此问题。当LCS应用字符串和给定字符串的反向时,它会给出字符串中存在的最长的回文子序列。

让长度为string_length的给定字符串的最长回文子序列长度为palin_length。然后(string_length - palin_length)给出要删除以将字符串转换为回文所需的字符数。因此,如果(string_length - palin_length)&lt; = k ,给定的字符串是k-palindrome。

让我举几个例子,

初始字符串: madtam (string_length = 6)

最长的回文序列:女士(palin_length = 5)

非贡献字符数:1(string_length - palin_length)

因此该字符串是k-回文,其中k> = 1。这是因为您需要删除最近 k个字符(k或更少)。

以下是代码段:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAX 10000

int table[MAX+1][MAX+1];
int longest_common_subsequence(char *first_string, char *second_string){
    int first_string_length = strlen(first_string), second_string_length = strlen(second_string);
    int i, j;
    memset( table, 0, sizeof(table));
    for( i=1; i<=first_string_length; i++ ){
        for( j=1; j<=second_string_length; j++){
            if( first_string[i-1] == second_string[j-1] )
                table[i][j] = table[i-1][j-1] + 1;
            else
                table[i][j] = max(table[i-1][j], table[i][j-1]);
        }
    }
    return table[first_string_length][second_string_length];
}

char first_string[MAX], second_string[MAX];

int main(){
    scanf("%s", first_string);
    strcpy(second_string, first_string);
    reverse(second_string, second_string+strlen(second_string));
    int max_palindromic_length = longest_common_subsequence(first_string, second_string);
    int non_contributing_chars = strlen(first_string) - max_palindromic_length;
    if( k >= non_contributing_chars)
        printf("K palindromic!\n");
    else 
        printf("Not K palindromic!\n");
    return 0;
}

答案 5 :(得分:0)

我设计了一个纯粹基于递归的解决方案 -

public static boolean isKPalindrome(String str, int k) {
            if(str.length() < 2) {
                return true;
            }

            if(str.charAt(0) == str.charAt(str.length()-1)) {
                return isKPalindrome(str.substring(1, str.length()-1), k);
            } else{
                if(k == 0) {
                    return false;
                } else {
                    if(isKPalindrome(str.substring(0, str.length() - 1), k-1)) {                        
                        return true;
                    } else{
                        return isKPalindrome(str.substring(1, str.length()), k-1);
                    }                   
                }
            }
        }

上述实现中没有while循环,如接受的答案中所示。 希望它可以帮助有人寻找它。

答案 6 :(得分:0)

这是一个常见的面试问题,对于没有人提到动态编程,我并不感到惊讶。此问题表现出最佳的子结构(如果字符串是k回文,则某些子字符串也是k回文),并且子问题重叠(解决方案需要多次比较同一子字符串)。

这是编辑距离问题的一种特殊情况,在该情况下,我们仅通过删除一个或两个字符串中的字符来检查字符串s是否可以转换为字符串p

让字符串为s及其相反的字符串rev。令dp[i][j]为将i的前s个字符转换为j的前rev个字符所需的删除数目。由于必须在两个字符串中都进行删除,因此如果为dp[n][n] <= 2 * k,则该字符串为k回文。

基本情况:当一个字符串为空时,另一个字符串中的所有字符都需要删除,以使其相等。

时间复杂度:O(n^2)

标量代码:

def kPalindrome(s: String, k: Int): Boolean = {
    val rev = s.reverse
    val n = s.length
    val dp = Array.ofDim[Int](n + 1, n + 1)

    for (i <- 0 to n; j <- 0 to n) {
      dp(i)(j) = if (i == 0 || j == 0) i + j
      else if (s(i - 1) == rev(j - 1)) dp(i - 1)(j - 1)
      else 1 + math.min(dp(i - 1)(j), dp(i)(j - 1))
    }
    dp(n)(n) <= 2 * k
}

由于我们正在执行自下而上的DP,因此优化是在任何时间i == j && dp[i][j] > 2 * k都返回false,因为所有后续i == j必须更大。