[LeetCode#214] Shortest Palindrome

本文探讨了如何通过在字符串前添加字符形成最短回文串的问题,详细分析了求解过程并给出了多种解决方案,包括动态规划方法和利用KMP算法优化的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem:

Given a string S, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.

For example: 

Given "aacecaaa", return "aaacecaaa".

Given "abcd", return "dcbabcd".

General Analysis:

I think this problem is not hard to solve. 
This problem is an variant of traditional Palindrome problem. It is not hard, but need a sincere inference. 

Apparently, we could alway turn s into a Palindrome by appending character at the front. 
Since we try to get the shortest Palindrome, we should try our best the take advantage of existing palindrome in the s. 
We hope the internal palindrome as long as enough. However, not all palindrome in the s could be used. We must guarantee the current palindrome would not destory the future possiblity of reaching a new palindrome. 

The max-length palindrome must start from the first character of s, otherwise, it is not valid!
s(sts)
Even though sts is a plindrome of length = 3. we can't use it. We only add characters for those outside the internal Palindrome.
However,no character add at the left side could wipe out a charcter remained at left side.
if (i == 0 && (j-i+1) > max) {
    max = j-i+1;
    start = i;
    end = j;
}
Idea: Don't disturb the internal palindrome we should use, but it should also not block our way to append new characters at left side to the overall string as a palindrome.

Inefficient Solution 1:

 public String shortestPalindrome(String s) {
        if (s == null || s.length() == 0)
            return "";
        int m = s.length();
        boolean[][] check_board = new boolean[m][m];
        int max = 0, start = 0, end = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j <= i; j++) {
                if (i == j)
                    check_board[i][j] = true;
                if (j-i <= 2)
                    check_board[i][j] = (s.charAt(i) == s.charAt(j));
                if (j-i > 2)
                    check_board[i][j] = check_board[i+1][j-1] && (s.charAt(i) == s.charAt(j));
                if (i == 0 && (j-i+1) > max) {
                    max = j-i+1;
                    start = i;
                    end = j;
                }
            }
        }
        StringBuffer buffer = new StringBuffer();
        for (int i = end+1; i < m; i++) {
            buffer.append(s.charAt(i));
        }
        return buffer.reverse().toString() + s; 
    }
    
The above solution is right, but it could have "exceed memory" problem when the string is too long, since the check_board take O(n^2) memory. 

Inefficient Solution 2:

public class Solution {
    public String shortestPalindrome(String s) {
        if (s == null || s.length() == 0)
            return "";
        int m = s.length();
        boolean[] check_board = new boolean[m];
        int start = 0, end = 0, max = 0;
        for (int j = 0; j < m; j++) {
            for (int i = 0; i <= j; i++) {
                   if (j == i) {
                       check_board[i] = true;
                   } else if(j - i == 1) {
                       check_board[i] = (s.charAt(i) == s.charAt(j));
                   } else {
                       check_board[i] = (s.charAt(i) == s.charAt(j)) && check_board[i+1];
                   }
                   if (i == 0 && check_board[i] == true) {
                       if (j-i+1 > max) {
                           max = j - i + 1;
                           start = i;
                           end = j;
                       }
                   }
            }
        }
        String ret = s;
        for (int i = end + 1; i < s.length(); i++) {
            ret = s.charAt(i) + ret;
        }
        return ret;
    }
}


I have implemented above upgraded version of solution, using dynamic programming through one dimensional array O(n). But since we need to compute all valid index pairs (i , j). The time complexity of this algorithm is O(n^2). For a long string, it would encounter the TLE problem. 
Note in the above solution:
2.1. we append no " " on before, since the only transitional equation needs refering other check_board's state is  "compute check_board[i, j] through  check_board[i+1, j-1]", which means we have no need to worry about the possible of exceed boundary. 

2.2. The code block of computing the check_board. 
check_board[i, j] : i represents row index, j represents column index.
check_board[i, j] = (s.charAt(i) == s.charAt(j)) && check_board[i+1, j-1];

Write in one dimensional way:
for (int j = 0; j < m; j++) {
    for (int i = 0; i <= j; i++) {
        check_board[i] = (s.charAt(i) == s.charAt(j)) && check_board[i+1];
    }
}

At each loop, we fix j, then compute each (i, j) pair based on previous column's (i+1, j-1)
You may wander why there is no exceed at row index? since we have i+1, and the maximum "i" we have computed at previous column is "j-1" (the element on diagonal line). That becase the maximum element we actually need to use is (j-1, j-1).
Cause:
a. when i = j - 1; j = j
if (j - i == 1)
    check_board[i] = (s.charAt(i) == s.charAt(j));

b. when i = j; j = j
if (i == j)
    check_board[i][j] = true;

Only when i = j-2; j = j, we need to use check_board[j-1][j-1];

 

Analysis:

The above solution still faces the problem of TLE!!!
We are very clear about that : our goal is to find out the longgest plindrome starting from the first character of String s. Actually we could covert it into an another problem, and use a classic algortihm to solve it.
The longest palidrome(start from index 0) is s 
====
The longest common(prefix/suffix) in string: s + "#" + s.reverse()

Why?
We know that if a string is a plindrome. The string must equal to it is reversed form.
s   =           "abacfg"
reversed_s   =  "gfcaba"
new_s = "[aba]cfg#gfc[aba]"

Thus we covert the original problem into its dual form!!! 
By chance we a classic algorithm: KMP for computing the longest "prefix/suffix" in a string. 
The algorithm is ver very elegant!!!

Reference:
https://leetcode.com/discuss/52564/a-kmp-based-java-solution-with-explanation
http://blog.youkuaiyun.com/v_july_v/article/details/7041827
http://www.rudy-yuan.net/archives/182/

The most important part of algorithm is how to take advantage of a next array. 
next[i]: the length of longest prefix/suffix from s[0 i], next[i] is the index after the longest prefix in s[0 i]. 
Since the s start from 0, s.charAt(next[i]) actually represents the the charcter after the longest prefix.

Intially, I have implemented following wrong solution.

Wrong Soltuion:

public class Solution {
    public String shortestPalindrome(String s) {
        if (s == null)
            throw new IllegalArgumentException("s is null");
        int len = s.length();
        if (len <= 1)
            return s;
        String new_s = s + "#" + new StringBuffer(s).reverse().toString();
        int[] next = new int[new_s.length()];
        int k = 0;
        for (int j = 1; j < new_s.length(); j++) {
            while (k > 0 &&  new_s.charAt(k) != new_s.charAt(j)) {
                k = next[k];
            }
            next[j] = k + (new_s.charAt(k)  == new_s.charAt(j) ? 1 : 0);
        }
        return new StringBuffer(s.substring(next[new_s.length()-1])).reverse().toString() + s;
    }
}

Mistake Analysis:

M1: Note fully understand meanning of k in the invariant, thus fail to update it properly.
int k = 0;
while (k > 0 &&  new_s.charAt(k) != new_s.charAt(j)) {
    k = next[k];
}
next[j] = k + (new_s.charAt(k)  == new_s.charAt(j) ? 1 : 0);
--------------------------------------------------------------------
Too compute the next[j]: s[0, j]'s longest common "prefix/suffix", we want first take advantage of next[j-1]'s, which alreay computed the longest common"prefix/suffix" information from s[0, j-1].
And the k is actually the first character after a valid longest common "prefix/suffix", which is also the length of the the valid longest common "prefix/suffix".
Fix:
Apparently the inital value of k should be next[j-1].
int k = next[j-1];
The we keep on shrinking the the common longest prefix if "new_s.charAt(k) != new_s.charAt(j)", which means the current longest prefix "k" is no use, we have to shrink into a shorter common prefix. 
k = next[k-1];
Note: there is k = next[k-1] rather than next(k). (k is the uncided charcter we need to compare, it stores in next[k-1])


Since the longest palinrome is stop at the prefix : next[new_s.length()-1], we could have following result.
return new StringBuffer(s.substring(next[new_s.length()-1])).reverse().toString() + s;

Solution:

public class Solution {
    public String shortestPalindrome(String s) {
        if (s == null)
            throw new IllegalArgumentException("s is null");
        int len = s.length();
        if (len <= 1)
            return s;
        String new_s = s + "#" + new StringBuffer(s).reverse().toString();
        int[] next = new int[new_s.length()];
        for (int j = 1; j < new_s.length(); j++) {
            int k = next[j-1];
            while (k > 0 &&  new_s.charAt(k) != new_s.charAt(j)) {
                k = next[k-1];
            }
            next[j] = k + (new_s.charAt(k)  == new_s.charAt(j) ? 1 : 0);
        }
        return new StringBuffer(s.substring(next[new_s.length()-1])).reverse().toString() + s;
    }
}

 

转载于:https://www.cnblogs.com/airwindow/p/4815070.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值