LeetCode第五题:寻找最长回文子串

LeetCode第五题:

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

示例 1:

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

示例 2:

输入: "cbbd"
输出: "bb"

这道题做了是真的久。其实想想并不难。一开始的时候是算法完全错了,进入了思维的误区。一直在debug,不断测试,然后都是部分测试可以通过,部分就是不通过。以为自己只是没有考虑到一些边界问题,然后每改一次,就多通过一些,但是任然有的会错误。到第二天依然不行。中文睡午觉得时候,没睡着,一直在脑中复现,突然就发现是我写的算法本身的问题。 然后就开始想别的算法。想到了一个递归算法,这回没错,但是当字符串很长的时候,就会栈溢出,不能满足题目需要。然后就把递归算法改成了普通循环,这回又变成了超时。真的是在做这题的时候什么都遇到了。分析了一下时间复杂的,时间复杂度是 n^3 。最后是看了分析,最后写了本文最后的算法,时间复杂的n^2.

第一次尝试

一开始没有仔细想,就开始做了。于是就想错了。算法如下:

算法一:
class Solution {
    public String longestPalindrome(String s) {
                //子串长度
        int slength = 0;
        //长串移位
        int move = 0;
        //长串长度
        int l = s.length();
        //字符数组个数
        int m = 0;
        //选定的数组序号
        int n = 0;
        //记录长度
        int t = 0;
        char[][] stringChar = new char[1000][l];
        char[] chars = s.toCharArray();
        int j = l - 1;
        while (slength <= l - move) {
            //组内循环号
            int k = 0;
            boolean flag = false;
            for (int i = 0; i < l && j >= 0; i++) {
                if (chars[i] == chars[j]) {
                    stringChar[m][k] = chars[i];
                    flag = true;
                    k++;
                    j--;
                    continue;
                }
                if (flag && k > t) {
                    int temp = n;
                    n = m;
                    t = k ;
                    m=temp;
                //    m++;
                    k = 0;
                    j = l-move-1;
                    flag = false;
                }
                if (flag) {
                    j = l-move-1;
                //    m++;
                    flag = false;
                }
            }
            if (flag && k > t) {
                n = m;
                t = k ;
            }
            slength = t;
            move++;
            j = l-move-1;
            if (j<=0) break;
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0;i<t;i++){
            sb.append(stringChar[n][i]);
        }
        return sb.toString();
    }
}

主要的思想是把该字符串逆转,想想有两个指针,一开始分别置于正字符串和反字符串的头上。

第一次
cbada 
adabc

第二次
cbada 
 adabc

第三次
cbada 
  adabc

如果不匹配,则正字符串的指针移位,反字符串不动。如匹配,则正反同时移位,直到不匹配,将匹配成功的子字符串与一开始的比较,取长者。

总之这个过程就是不断的移位,匹配的过程。可以看到,代码中也是写了很多的期指变量,看着难受,写着也头疼。花费了大量的时间解决边界问题,但是最重要的是,该问题不能被这样解决!如下:

tabgat
tagbst

可以看tabgat到,字串tabgat 的最大回文字串就是单个字符。但是使用算法一,就会被判断为最长回文子串为ta ,所以该算法根本不可行。

第二次尝试

还是躺着比较容易思考.

否决了之前的算法后,一切都舒畅多了。突然想到,寻找最长子串可以用递归的思路来想。

当一个问题可以分解为类型相同,但规模不同的子问题的时候,且有最终状态,就可以用递归解决。

  1. 寻找最字符串s 的最长回文串,找到则返回。
  2. 寻找比s 的长度短1的字符串的最长回文串
  3. 当字符串为空或长度为一时,则直接返回。

这里我也遇到了一个问题,那就是在第二步寻找子问题的时候。因为比原串s 短1的子串有两个,去头或去尾。然后一开始写出了错误的递归算法:

class Solution {
        public String longestPalindrome(String s) {
        if (isPalinedrome(s)) return s;
        String s1 = s.substring(0,s.length()-1);
        String s2 = s.substring(1,s.length());
        return findNext(s1,s2);
    }
    //判断是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一个子串
    String findNext(String s1, String s2) {
        if (isPalinedrome(s1)) return s1;
        if (isPalinedrome(s2)) return s2;
        return findNext(s1.substring(0,s1.length()-1),s2.substring(1,s2.length()));
    }
}

看起来好像情况都考虑了,实则没有。在第20行这里。会一直执行,直到返回回文,或到最后返回单个字符。s2永远不会被考虑。

后来我就想到了如下的算法:

class Solution {
     public String longestPalindrome(String s) {
        return findNext(s, s.length(), 0);
    }
    //判断是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一个子串 s母串,n子串长度,index子串序号
    String findNext(String s, int n, int index) {
        if (isPalinedrome(s.substring(index, index + n))) return s.substring(index, index + n);
        else {
            if (index < s.length() - n) index++;
            else {
                n--;
                index = 0;
            }
        }
        return findNext(s, n, index);
    }
}

依次寻找下一个字符串。可行。然后使用for 循环,改成了非递归的方式:

class Solution {
     public String longestPalindrome(String s) {
        return findNext(s, s.length(), 0);
    }
    //判断是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一个子串 s母串,n子串长度,index子串序号
    String findNext(String s, int n, int index) {
        while (!isPalinedrome(s.substring(index, index + n))) {
            if (index < s.length() - n) index++;
            else {
                n--;
                index = 0;
            }
        }
        return s.substring(index, index + n);
    }
}

依次验证每一个子串是不是回文。子串共有(1+n)*n/2个,每个子串验证回文时间复杂度为n,所以时间复杂的为n^3。在LeetCode验证超时。

今天写了最终版。分别以每个字符或俩字符间隙为中心,从外扩散。找到以之为中心的最长回文,与原来的比较,取长者,最终返回。时间复杂度为n^2.

class Solution {
        public String longestPalindrome(String s) {
        String palindrome = "";
        char[] chars = s.toCharArray();
        for (int i = 0; i < (2 * s.length())-1; i++) {
            int big = 0;
            while (i + big < ((2 * s.length())-1) && i - big >= 0 && (chars[(i - big)/2] == chars[(i + big)/2])) {
                if (i % 2 != 0) {
                    if (big == 0) {big++;}
                    else if (chars[(i - big)/2] == chars[(i + big)/2]) {
                        big = big + 2;
                    }
                }
                else {
                    if (chars[(i - big)/2] == chars[(i + big)/2]) {
                        big = big + 2;
                    }
                }
            }
            big = big - 2;
            if (big >= palindrome.length()) {
                palindrome = s.substring((i - big) / 2, (i + big) / 2+1);
            }
        }
        return palindrome;
    }
}

小结

这道题我真的遇到了好几种问题,也写了好久。最大的感受就是其实只要好好想清楚了,写起来就是一会的事。一旦陷入思维的误区,还不走出来,就会万劫不复。动手前,真的要好好想清楚思路,不要太着急。

posted @ 2018-11-29 16:51  一把水果刀  阅读(1388)  评论(0编辑  收藏  举报