zoukankan      html  css  js  c++  java
  • [LeetCode#214] Shortest Palindrome

    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.csdn.net/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;
        }
    }
  • 相关阅读:
    1-4-04:奇偶ASCII值判断
    1-4-03:奇偶数判断
    1-4-02:输出绝对值
    1-4-01:判断数正负
    1-3-20:计算2的幂
    1-3-19:A*B问题
    1-3-18:计算三角形面积
    Use PIVOT Table in SQL Server
    Pivoting DataTable Simplified
    Pivot Methods 行列转换
  • 原文地址:https://www.cnblogs.com/airwindow/p/4815070.html
Copyright © 2011-2022 走看看