动态规划,最长公共子序列、最长回文子串

本文深入讲解动态规划在解决最长公共子序列及最长回文子串问题中的应用,介绍状态转移方程的设计思路,并提供具体实现代码。

目录

最长公共子序列

总结动态规划

最长回文子串


动态规划不同与排序那些固定解法,不同题有相似套路,不同的规律需要自己找

最长公共子序列

两个字符串,找出最长的公共部分,公共部分只需要保证字符顺序相同、不需要必须相连,比如"randomx","androidx",a an  and  ando  andox都是公共子序列,最长的是andox.

下图是计算的表达式,C[i,j],比如random是第一个字符串,i表示它的第几位,android是第二个字符串,j表示其第几位,C[i,j]表示第一个字符串的前i位和第二个字符串的前j位,这两个新字符串的最大公共子序列。

如果i=0或j=0,C[i,j]=0。如果i,j>0,xi=yj,C[i,j]=C[i-1,j-1]+1,怎么得出的这个关系?因为公共子序列不需要相连,只要顺序相同,既然xi=yj说明公共子序列又加长一位。

如果xi!=yj,但xi有可能=y(j-1),x(i-1)有可能=y(j),这样也会使C[i,j-1]比C[i-1,y-1]大1, 或者C[i-1,j]比C[i-1,j-1]大1。 虽然xi!=yj,依然有可能因为ij增大,最长子序列增大的可能!所以要在C[i-1,j]和C[i,j-1]两者间挑个大的。

把上面的关系式用代码表示:

public class LCS {

    public int findLCS(String A, String B) {
        char[] a = A.toCharArray();
        char[] b = B.toCharArray();
        int n = A.length();
        int m = B.length();
        int[][] dp = new int[n][m];

        //第一行
        for (int i = 0; i < n; i++) {
            if (a[i] == b[0]) {
                //dp[0][i] = 1; 可别写反
                dp[i][0]=1;
                for (int j = i + 1; j < n; j++) {
                    dp[j][0] = 1;
                }
                break;
            }
        }

        //第一列
        for (int i = 0; i < m; i++) {
            if (a[0] == b[i]) {
                dp[0][i] = 1;
                for (int j = i + 1; j < m; j++) {
                    dp[0][i] = 1;
                }
                break;
            }
        }

        //根据左边、上边 的值,计算当前
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                if (a[i] == b[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.println();
        }
        return dp[n - 1][m - 1];
    }

    public static void main(String[] args) {
        LCS lcs = new LCS();
        System.out.println("最长公共子序列"+lcs.findLCS("random","android"));
    }
}

总结动态规划

动态规划关键在于找到

  • 状态转移方程:f(n)的值可以根据f(n-1)或者f(n-x)推导出来,根据之前的值推导,而不是重新计算,但是有可能把0-n所有的f(n)都计算,只是后面的依赖前面的结果,让前面的计算没有浪费、
  • dp[][]数组该存储什么?这极有可能是二维数组

什么问题适合动态规划?

  • 大问题可以拆成小问题,f(n)可以从f(1) f(n-x)得到,f(n)可以依赖他们从而得到结果

最长回文子串

aba   ababa   ccdd  这种从前读和从后读一样的字符串就叫回文子串

我自己想的方法

  • 写一个判断是否回文子串的方法,按字符顺序遍历字符串,把每个字符位置的最长回文子串保存到数组对应位置。
  • 试当前遍历字符j和前面能否组成回文,把j作为字符串结尾,以谁开头?从0开始试到x,看x到j是不是回文子串,如果是并且长度比原来的回文长就是新的最长回文,一直试到字符串j-x比保存的最长回文长度小为止,如果试到了说明这是目前最长回文
  • 虽然f(n)的值也借助了f(n-1),但只是和新值比较得来,并不是直接直接由f(x)得到f(n),还是的计算新的
  • 时间复杂度是O(n^3),在力扣提交直接超时了
public class LongestPalindrome {

    public String longestPalindrome(String s) {
        int n = s.length();
        String [] palindrome = new String[n];
        for (int i=0;i<n;i++){
            if (i==0){
                palindrome[i]=s.substring(0,1);
                continue;
            }

            String last = palindrome[i - 1];
            palindrome[i]=palindrome[i - 1];
            for (int j=0;j<=i-last.length();j++){
                if (isPalindrome(s.substring(j,i+1))){
                    palindrome[i]=s.substring(j,i+1).length()>palindrome[i-1].length()?s.substring(j,i+1):palindrome[i-1];
                    break;
                }
            }
        }

        String max = "";
        for (int i=0;i<n;i++){
            System.out.println(palindrome[i]);
            max=max.length()>palindrome[i].length()?max:palindrome[i];
        }
        return max;
    }

    private Boolean isPalindrome(String str) {
        int n = str.length();
        char[] chars = str.toCharArray();

        for (int i = 0; i < n / 2; i++) {
            if (chars[i] != chars[n - 1 - i]) {
                return false;
            }
        }
        return true;
    }


    public static void main(String[] args) {
        LongestPalindrome longestPalindrome = new LongestPalindrome();
        System.out.println("最长回文字段:"+longestPalindrome.longestPalindrome("ababababa5645634"));

    }
}

动态规划解法

  • 用 P(i,j)P(i,j) 表示字符串 s的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串,注意P(i,j)是Boolean值
  • 状态转移方程:P(i,j)=(P(i+1,j−1) or j-i<3)and(Si​==Sj​),这样通过P(i+1,j-1)就可以得到P(i,j)
  • 如果Si​==Sj 且P(i+1,j−1)为true,则P(i,j)是true。 j-i<3代表字符串长度小于等于3,如 a、aa、aba
  • 图中表的左下方不是合法字符串,右边界比左边界小;因为P(i+1,j−1)位于P(i,j)左下方,所以先升序填列,在升序填行,这样左下方始终有值
  • 不保存回文子串,只保存最长回文的左右边界,这样省空间。。。这也是之前没想到的
public class Solution {

    public String longestPalindrome(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);
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值