zoukankan      html  css  js  c++  java
  • 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;
        }
    }
    

    小结

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

  • 相关阅读:
    CSP-S2-2019游记
    【BZOJ2301】【HAOI2011】Problem B
    【NOIp2017】宝藏
    【NOIp2016】天天爱跑步
    【NOIp2018】保卫王国
    【BZOJ2159】Crash的文明世界
    Van爆零赛总结
    【ZJOI2016】小星星
    【CF1025D】Recovering BST
    【HAOI2016】字符合并
  • 原文地址:https://www.cnblogs.com/fruitknife/p/10039436.html
Copyright © 2011-2022 走看看