LeetCode 5.最长回文字串 Longest Palindromic Substring (C语言)

题目描述:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”
输出: “bb”

题目解答:

方法1:动态规划

求最长、最大值这种问题,一般可能会用到动态规划,所以需要寻找之间的规律,在该题中如果位置 i 是回文串,那与i - 1位置有什么联系呢?
假定已经存储好0 ~ (i - 1)每个位置的最长回文子串长度(以自己向前的回文子串,包括自己),对于 i 位置的字符,如果i - 1位置回文串前边那个字符与 i 位置字符相同s[i - 1 - dp[i - 1]] == s[i],则可以延长回文串,即dp[i] = dp[i - 1] + 2,如果不等,则需要从i - dp[i - 1]开始向 i 遍历,寻找回文串,找到则存入 dp[i] ,跳出该层循环。另外在遍历 i 的同时,记录最大长度和有最大长度的位置。运行时间4ms,代码如下。

bool isPalind(char* s, int len) {
    int i = 0, j = len / 2;
    for(i = 0; i < j; i++) {
        if(s[i] != s[len - 1 - i])
            return false;
    }
    return true;
}
char* longestPalindrome(char* s) {
    int len = strlen(s);
    int* dp = (int*)malloc(len * sizeof(int));
    dp[0] = 1;
    int i = 0, max = 1, index = 0, j = 0;
    for(i = 1; i < len; i++) {
        if((i - 1 - dp[i - 1] >= 0) && s[i] == s[i - 1 - dp[i - 1]])
            dp[i] = dp[i - 1] + 2;
        else {
            for(j = i - dp[i - 1]; j <= i; j++) {
                if(isPalind(s + j, i - j + 1)) {
                    dp[i] = i - j + 1;
                    break;
                }
            }
        }
        if(dp[i] > max) {
            max = dp[i];
            index = i - dp[i] + 1;
        }
    }
    s[index + max] = '\0';
    free(dp);
    return s + index;
}

上述解法中对于s[i - 1 - dp[i - 1]] != s[i]的情况下,需要重新从i - dp[i - 1]开始遍历,实际上这个位置时间复杂度还挺高。所以换一种动态规划的思路。
假设我们存储一个二维数组,dp[i][j]表示从字符串 s 的 i 位置到 j 位置,其是否为一个回文串,是则标记为true,不是则标记为false。易知对角线上必定为true,对于dp[i][j],如果s[i] == s[j] && dp[i + 1][ j - 1],则dp[i][j] = true,否则为false;因为最终结果是对称的,所以只选用右上三角或左下三角即可,为了更加直观理解,此处选用正方形的右上三角。因为在访问dp[i][j]时,dp[i + 1][ j - 1]应该已经被标记过,所以会涉及到一个遍历顺序的问题,应该时从下向上遍历行,从左向右遍历列。注意两个相同的字符相邻时,其也是回文串。运行时间32ms,代码如下。

char* longestPalindrome(char* s) {
    int len = strlen(s);
    bool** dp = (bool**)malloc(len * sizeof(bool*));
    dp[0] = 1;
    int i = 0, max = 1, index = 0, j = 0;
    for(i = 0; i < len; i++) {
        dp[i] = (bool*)calloc(len, sizeof(bool));
        dp[i][i] = true;
    }
    for(i = len - 1; i >= 0; i--) {
        for(j = i + 1; j < len; j++) {
            if(s[i] == s[j] && (dp[i + 1][j - 1] || i + 1 == j)) {
                dp[i][j] = true;
                if(j - i + 1 > max) {
                    max = j - i + 1;
                    index = i;
                } 
            }    
        }
    }
    s[index + max] = '\0';
    for(i = 0; i < len; i++)
        free(dp[i]);
    free(dp);
    return s + index;
}

方法2:中心扩散法

奇数长度回文串中,对于某字符,向左右两侧同时扩散,如果左右字符相同,继续向外扩散,直到两侧字符不同,返回长度;偶数长度时,首先需要相邻的两个字符相同,然后再同时向左右扩散,直到不同返回长度。所以遍历 i ,i 向两边扩散,找到其最长回文串,返回长度,比较奇数长度和偶数长度,记录最大值,并记录起始索引。运行时间4ms,代码如下。

int getPalind(char* s, int left, int right, int n) {
    int  i = 0;
    while(left >= 0 && right < n) {
        if(s[left] != s[right])
            break;
        else {
            left--;
            right++;
        }
    }
    return right - left - 1;
}
char* longestPalindrome(char* s) {
    int len = strlen(s);
    int i = 0, max = 0, index = 0;
    for(i = 0; i < len; i++) {
        int odd = getPalind(s, i, i, len);
        int even = getPalind(s, i, i + 1, len);
        if(odd > max) {
            max = odd;
            index = i - (odd >> 1);
        }
        if(even > max) {
            max = even;
            index = i + 1 - (even >> 1);
        }
    }
    s[index + max] = '\0';
    return s + index;
}

你可能感兴趣的:(LeetCode,C语言)