java数据结构与算法刷题-----LeetCode5:最长回文子串

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

java数据结构与算法刷题-----LeetCode5:最长回文子串_第1张图片

  1. 思路分析
  1. 参考代码注释
  1. 代码
    java数据结构与算法刷题-----LeetCode5:最长回文子串_第2张图片
class Solution {
    /**
        方法一:Manacher 算法 O(n) 空间复杂度O(n) 。非常复杂,推荐先掌握方法二
        比如abaaba这个字符串
        先统一奇偶性,变成#a#b#a#a#b#a#,就是用一个特殊字符,将每一个字符分隔,首尾也要
            那么偶数个的字符串abaaba的中心就是aba(#)aba中间的#号
            奇数个的字符串aba的中心是#a#b#a# 正好是中间的b
            而且无论源串是奇数还是偶数,加完#号后,都是奇数
            而且加完后的回文串长度是2*n+1.(aba => #a#b#a# = 2*3+1 = 7. abba => #a#b#b#a# = 2*4+1 = 9)
            因此我们还原时,只需要(len-1)/2 就可以了。(#a#b#a# = (7-1)/2 = 3. #a#b#b#a# = (9-1)/2 = 4)
        
     */
    public String longestPalindrome(String s) { 
        int start = 0, end = -1;
        StringBuffer t = new StringBuffer("#");
        for (int i = 0; i < s.length(); ++i) {
            t.append(s.charAt(i));
            t.append('#');
        }
        t.append('#');
        s = t.toString();

        List<Integer> arm_len = new ArrayList<Integer>();
        int right = -1, j = -1;
        for (int i = 0; i < s.length(); ++i) {
            int cur_arm_len;
            if (right >= i) {
                int i_sym = j * 2 - i;
                int min_arm_len = Math.min(arm_len.get(i_sym), right - i);
                cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
            } else {
                cur_arm_len = expand(s, i, i);
            }
            arm_len.add(cur_arm_len);
            if (i + cur_arm_len > right) {
                j = i;
                right = i + cur_arm_len;
            }
            if (cur_arm_len * 2 + 1 > end - start) {
                start = i - cur_arm_len;
                end = i + cur_arm_len;
            }
        }

        StringBuffer ans = new StringBuffer();
        for (int i = start; i <= end; ++i) {
            if (s.charAt(i) != '#') {
                ans.append(s.charAt(i));
            }
        }
        return ans.toString();
    }

    public int expand(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            --left;
            ++right;
        }
        return (right - left - 2) / 2;
    }
    /**
        方法二:中心扩展法(中心枚举) O(n^2) 空间复杂度O(1),推荐的方法,简单,8ms
        比如abcba这个字符串
        我们先找到最开始的a,它本身是回文串,然后中心扩散,发现左边没有,那么长度为1(right-left-1)
        然后找到b,本身回文串,扩散,a!=c.因此abc不是回文串,只有b本身,长度为1
        然后c为中心,扩散,b = b,bcb是回文串,可以继续扩散。a=a,那么abcba是回文串,再次扩散,没有了,长度为5

        例外的abccba这样的字符串也是回文串,cc为中心
        同样的abcccba以c为中心,abccccba,以cc,就是中间的c(cc)c,为中心

        如何获取找到的回文串下标呢?
        abccba是回文串,长度len为6,中心为cc,
        循环下标index为2指向中心,也就是说len的一半,让index减去,就是起始下标
        但是下标从0开始,所以,(len-1)/2才对,因此
        start = i-(len-1)/2
        同理end = i+len/2; 
     */
    public String longestPalindrome1(String s) {
        if(s==null || s.length()<1) return"";

        char c[] = s.toCharArray();
        int start = 0,end = 0;
        for(int i = 0;i<s.length();i++){
            int len1 = expandAroundCenter(c,i,i);
            int len2 = expandAroundCenter(c,i,i+1);
            int len = Math.max(len1,len2);
            if(len > end-start){
                start=i-(len-1)/2;
                end = i+len/2;
            }
        }
        return s.substring(start,end+1);
    }
    private int expandAroundCenter(char[] c,int i,int j){
        while(i>=0 && j<c.length && c[i] == c[j]){
            i--;j++;
        }
        return j - i - 1;
    }

    //方法三:动态规划 O(n^2) 空间复杂度O(n^2) 不推荐,浪费资源,160ms
    // 字符串[b,a,b,a,d]
    // 下标  [0,1,2,3,4]

    //动态规划:[0~4]这个串babad是否回文,取决于0和4对应字符(b,d)是否相同,以及它中间的1~3对应的子串(aba)是否是回文串
    //因为b和d不同,所以不是回文串
    //[1~3]aba这个串是否回文,取决于1和3对应字符a,a是否相同,以及中间2对应的子串,b是不是回文串,
    //因为a=a,并且b是回文串(1个字符就是回文串),所以[1~3]是回文串,长度为3-1+1 = 3(j-i+1)
    //规划表 arr[i][j] = arr[i-1][]
    //  0       1       2       3       4
    //0 true    false   true    false   false
    //1         true    false   true    false
    //2                 true    false   fasle
    //3                         true    fasle
    //4                                 true
    public String longestPalindrome2(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        int maxLen = 1;
        int begin = 0;
        // dp[i][j] 表示 s[i..j] 是否是回文串
        boolean[][] dp = new boolean[len][len];
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        char[] charArray = s.toCharArray();
        // 递推开始
        // 先枚举子串长度
        for (int L = 2; L <= len; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < len; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= len) {
                    break;
                }

                if (charArray[i] != charArray[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    }
    
}

刷题一定要坚持,总结套路,不单单要把题做出来,要举一反三,也要参考别人的思路,学习别人解题的优点,找出你觉得可以优化的点。

  1. 单链表解题思路:双指针、快慢指针、反转链表、预先指针
  1. 双指针:对于单链表而言,可以方便的让我们遍历结点,并做一些额外的事
  2. 快慢指针:常用于找链表中点,找循环链表的循环点,一般快指针每次移动两个结点,慢指针每次移动一个结点。
  3. 反转链表:通常有些题,将链表反转后会更好做,一般选用三指针迭代法,递归的空间复杂度有点高
  4. 预先指针:常用于找结点,比如找倒数第3个结点,那么定义两个指针,第一个指针先移动3个结点,然后两个指针一起遍历,当第一个指针遍历完成,第二个指针指向的结点就是要找的结点
  1. 数组解题思路:双指针、三指针,下标标记
  1. 双指针:多用于减少时间复杂度,快速遍历数组
  2. 三指针:多用于二分查找,分为中间指针,左和右指针
  3. 下标标记:常用于在数组范围内找东西,而不想使用额外的空间的情况,比如找数组长度为n,元素取值范围为[1,n]的数组中没有出现的数字,遍历每个元素,然后将对应下标位置的元素变为负数或者超出[1,n]范围的正数,最后没有发生变化的元素,就是缺少的值。
  1. 栈解题思路:倒着入栈,双栈
  1. 倒着入栈:适用于出栈时想让输出是正序的情况。比如字符串’abc’,如果倒着入栈,那么栈中元素是(c,b,a)。栈是先进后出,此时出栈,结果为abc。
  2. 双栈:适用于实现队列的先入先出效果。一个栈负责输入,另一个栈负责输出。

你可能感兴趣的:(算法,java,算法,leetcode)