【问题标题】:how to efficiently get minimal number removals of characters of the string to convert it to palindrome如何有效地删除字符串中的字符以将其转换为回文
【发布时间】:2016-11-16 19:51:58
【问题描述】:

关于how to convert a string to palindrome with minimum number of removals of characters of the string?的问题。我编写程序来测试接受的答案。但是递归需要太多时间。如何解决或改进这个问题?以下是接受的答案:

令 dp[i, j] = 将子串 [i, j] 转换为回文所需的最小删除次数。我们有:

dp[i, i] = 0 for all i (every single character is a palindrome) 到 找到 dp[i, j],让我们考虑一个随机字符串。我们有两个 可能性:

  1. 第一个和最后一个字符相等:a[i] == a[j]。在这种情况下, 我们可以将问题简化为找到最小字符数 需要删除以使子字符串 [i+1, j-1] a 回文。

  2. 第一个和最后一个字符不相等:a[i] != a[j]。 在这种情况下,我们需要删除其中一个。我们将删除 引导我们找到更好的解决方案。

/* remvoe the least characters to make a string be palindrome */

#include <stdio.h>
#include <string.h>

#define MAXLINE  4096

int func(char *p, int low, int high);
int min(int m, int n); // get the minimal value

int main(void)
{
    char    str[MAXLINE];
    int     ret;

    while (scanf("%s", str) != EOF) { // input in a loop
        ret = func(str, 0, strlen(str) - 1); // call func

        printf("%d\n", ret);
    }

    return 0;    
}

/* find the minimal number of characters in a string, 
 * which are needed removed to make the string be palindrome 
 */
int func(char *p, int low, int high)
{
    int     n;
    int     l;
    int     r;

    if (low >= high) {
        return 0;
    }

    if (p[low] == p[high]) { // needn't remove 
        n = func(p, low + 1, high - 1);          
    }
    else {
        l = func(p, low + 1, high);
        r = func(p, low, high - 1);
        n = min(l, r) + 1;
    } 

    return n;
}

/* return the minimal variable */
int min(int m, int n) 
{
    return (m < n ? m : n);

}

【问题讨论】:

  • 请定义“太多时间”。此外,如果您的代码正在运行并且您希望获得有关如何改进它的建议,那么Code Review 可能是您提出问题的更合适的地方。
  • 找到的代码足够快地处理短文本。在l = func(p, low + 1, high); 之后添加if (l== 0) return 1; 加快速度
  • @kaylum 我随机试了字符串“jfdasflkjdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddfjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj”,但是很久没有反应。
  • @chux 我已经添加了我的测试。
  • 你应该以某种方式记住递归调用。这会将您的指数时间解决方案变成 O(n^2) 时间解决方案。

标签: c algorithm


【解决方案1】:

您不应该递归调用它,因为这会导致多次执行相同的检查(某些范围将被检查多次,而实际上只需要一次检查)。相反,您应该使用“动态编程”方法,从下到上构建。这意味着你需要创建一个二维数组dp[i][j], i&lt;j,它存储了ij 范围内的最大回文长度。所以如果j=i+k 首先你继续为k=0 构建,然后为k=1 等等。

【讨论】:

    【解决方案2】:

    一个关键的改进是认识到,当字符串的只有一侧被淘汰时,另一侧必须有匹配(与另一侧的字符,即使是它自己),不然为什么不两边都消灭?

    当一个角色从一侧被移除时,从该侧向另一侧搜索以匹配另一侧的角色。 (总能找到匹配项。)这消除了许多不必要的递归路径。

    二次改进“短路”如下。无需测试其他组合,因为它们无法改善结果。

    if (left == 1) return 1;
    

    int func(const char *p, int low, int high) {
      int n;
      int left;
      int right;
    
      count++;
      if (low >= high) {
        return 0;
      }
    
      if (p[low] == p[high]) { // needn't remove
        n = func(p, low + 1, high - 1);
      } else {
    #if 0
        left = func(p, low + 1, high);
        // if (left == 0) return 1;
        right = func(p, low, high - 1);
        n = min(left, right) + 1;
    #else
        int delta;
        // remove low, keep high as part of palindrome
        delta = 1;
        while (p[low + delta] != p[high])
          delta++;
        left = func(p, low + delta, high) + delta;
        if (left == 1) return 1;
    
        // remove high, keep low as part of palindrome
        delta = 1;
        while (p[low] != p[high - delta])
          delta++;
        right = func(p, low, high - delta) + delta;
        if (right <= 2) return right;
        n = min(left, right);
    
        // remove first and last
        //int both = func(p, low + 1, high-1) + 1 + (high > (low + 1));
        int both = func(p, low + 1, high - 1) + 2;
        n = min(n, both);
    #endif
      }
      return n;
    }
    

    将鼠标悬停在OP's test string 的最终结果上(隐藏以防 OP 不想立即看到它。)

    count = 13090 ret = 45 str = 'jfdasflkjdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddfjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj'

    一个小的改进使用const。一些编译器会在知道缓冲区不变的情况下生成更高效的代码。无论如何,更好的编译器可能会检测到这一点。

    // int func(char *p, int low, int high)
    int func(const char *p, int low, int high)
    

    一些测试驱动代码

    #include <stdio.h>
    #include <string.h>
    
    #define MAXLINE  4096
    unsigned long long count = 0;
    
    int func(const char *p, int low, int high);
    int min(int m, int n); // get the minimal value
    
    void testfunc(const char *str) {
      count = 0;
      int ret = func(str, 0, (int) strlen(str) - 1); // call func
      printf(" count = %llu", count);
      printf(" ret = %d", ret);
      printf(" str = '%s' ++", str);
      puts("");
      fflush(stdout);
    }
    
    int main(void) {
      char str[MAXLINE];
      int ret;
    
      char t[] =
          "jfdasflkjdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
              "ddddddddddddddfjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj";
      for (size_t i = 0; t[i]; i++) {
        strncpy(str, t, i);
        str[i] = 0;
        testfunc(str);
      }
      return 0;
    }
    
    int min(int m, int n) {
      return (m < n ? m : n);
    }
    
    int func(const char *p, int low, int high) {
      ...
    

    【讨论】:

    • 我运行你的代码#else block,但它输出分段错误
    • @hel 也许是编辑错误?整个功能已发布。如果仍然存在段错误,请尝试更小的字符串并报告结果“输出分段错误”没什么好继续的。
    • 代码只有一个问题 - 缺乏记忆力,这几乎是 OP 目前需要关注的问题
    • @Yerken 不同意“只有 一个 问题” - 只有您的评论和答案的文本支持,而不是代码。有很多方法可以改进 OP 的代码。这个答案展示了另一种方式,它至少比 OP 的原始代码快数十亿倍并且不“记忆”——它当然具有较低的复杂度。它使递归中的修剪选择好多了。您提倡的“记忆”听起来不错,但尚未得到证明。
    • @hel 还在写这篇文章吗?有哪些未解决的问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-30
    • 2010-12-25
    • 1970-01-01
    • 2011-01-03
    • 2015-10-12
    • 1970-01-01
    相关资源
    最近更新 更多