【问题标题】:Determine if a given string is a k-palindrome确定给定字符串是否为 k-回文
【发布时间】:2026-01-17 13:15:01
【问题描述】:

我正在尝试解决以下面试练习题:

k-palindrome 是一个字符串,最多在删除时转换为回文 k 个字符。

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

约束:

S 最多有 20,000 个字符。
0 <= k <= 30

示例测试用例:

Input - abxa 1 
Output - YES 

Input - abdxa 1 
Output - NO

我决定的方法是采用所有可能的长度为 s.length - k 或更大的字符串组合,即 "abc" 和 k = 1 -> "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;
}

任何人都可以想出一种方法来实现这一点,或者展示一个更好的方法吗?

【问题讨论】:

  • Apache Commons,StringUtils.reverse。应该是:"abxa".equals(StringUtils.reverse("abxa"))
  • 你应该在你的问题中真正描述你的问题,而不是让它从你的 cmets 中推断出来,因为标题具有误导性。

标签: java algorithm palindrome


【解决方案1】:

我认为有更好的方法

package se.wederbrand.*;

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;

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

【讨论】:

  • 非常感谢!这很棒,而且比我的更好,子字符串可能比 char 数组更好。
  • 递归的最大深度应该是 K 并且在这种情况下。
  • NP。有时,像人类一样思考会有所帮助。这就是我在纸上解决问题的方法。祝面试顺利。
  • @AndreasWederbrand 方法 testIt 被调用的次数将是 (20000^2)*30,因为将有 20000^2 个不同的可能子字符串,并且每个子字符串可能有 30 个不同的 k。我不认为它应该跑得很快。在最坏的情况下,您的算法需要多长时间?
  • 这是指数时间,它可能不应该被标记为正确答案。最好使用动态编程方法。
【解决方案2】:

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

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

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

如果editDistance(S,reverse(S))

,则字符串S是K回文

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

举个例子会更清楚。

令 S=madtam 和 K=1。

要将 S 转换为 S 的反转(即 matdam),首先我们必须删除 S 中索引 3(基于 0 的索引)处的字符 't'。

现在中间字符串是 madam。然后我们必须在中间字符串的索引 2 处插入字符 't' 以获得“matdam”,它是字符串 s 的倒数。

如果你仔细看就会知道中间字符串“madam”是去掉k=1个字符得到的回文串。

【讨论】:

  • 嘿,我认为对于 K 回文,正确的条件应该是 editDistance(S, reverse(S))
  • 谢谢!!更新了答案:-)
【解决方案3】:

我找到了最长字符串的长度,这样在删除字符 >= k 后,我们将得到一个回文。我在这里使用了动态编程。我所考虑的回文不必是连续的。它和 abscba 一样,最长的回文长度为 4。

所以现在可以进一步使用它,这样只要 k >= (len - len of最长回文数),结果为真,否则为假。

 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];
    }

【讨论】:

    【解决方案4】:

    这是一个常见的面试问题,我对还没有人提到动态规划感到惊讶。这个问题展示了最优子结构(如果一个字符串是k-回文,一些子字符串也是k-回文),以及重叠子问题(解决方案需要多次比较相同的子字符串)。

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

    让字符串为s,其反向为rev。设dp[i][j] 是将s 的前i 字符转换为rev 的前j 字符所需的删除数。由于必须在两个字符串中进行删除,如果dp[n][n] &lt;= 2 * k,则该字符串是k-回文。

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

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

    Scala 代码:

    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,因此优化是在任何时候都返回 false i == j &amp;&amp; dp[i][j] &gt; 2 * k,因为所有后续的 i == j 必须更大。

    【讨论】:

    • 我也很惊讶没有人提到动态编程。如果你们都在这里使用任何其他方法,那么您将不会获得报价/另一次面试,因为这些解决方案具有可怕的时间复杂度(指数)。 @Abhijit,投赞成票。如果我是 OP,你会被标记为正确答案。
    【解决方案5】:

    感谢安德烈亚斯,该算法发挥了作用。这是我对任何好奇的人的实现。略有不同,但基本相同的逻辑:

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

    【讨论】:

    • 如果k == 0,您可以遍历数组,检查它是否是回文 - 无需进一步递归。另外,我不建议在每个递归步骤中创建char[](或String)——您可以只跟踪原始字符串中当前的左右索引。
    【解决方案6】:

    这个问题可以使用著名的Longest Common Subsequence(LCS)方法来解决。当 LCS 应用于字符串和给定字符串的反转时,它会为我们提供字符串中存在的最长回文子序列。

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

    让我举几个例子,

    初始字符串:ma​​dtam (string_length = 6)

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

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

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

    这里是sn-p的代码:

    #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;
    }
    

    【讨论】:

      【解决方案7】:

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

      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 循环,如已接受的答案。 希望它对寻找它的人有所帮助。

      【讨论】:

        【解决方案8】:
           public static boolean failK(String s, int l, int r, int k) {
        
                if (k < 0)
                    return false;
        
                if (l > r)
                    return true;
        
                if (s.charAt(l) != s.charAt(r)) {
                    return failK(s, l + 1, r, k - 1) || failK(s, l, r - 1, k - 1);
                } else {
                    return failK(s, l + 1, r - 1, k);
                }
            }
        

        【讨论】:

          最近更新 更多