关于动态规划的一点思考

109 阅读1分钟

动态规划核心就是需要利用之前的计算结果进行进一步计算来获取当前的结果,而能否利用上一步的计算结果至关重要

下面看这道题

5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:

输入: s = "babad"
输出: "bab"
解释: "aba" 同样是符合题意的答案。

示例 2:

输入: s = "cbbd"
输出: "bb"

如果字符串s是回文串,那么在字符串s两端分别增一个字符那么新得的字符串一定也是回文串,定义一个二维数组dp[i][j],表示从下表i到j的字符串是否是回文串,如果如果 dp[i+1][j-1]是回文串且 第i个字符和第j个字符相等,那么我们可以得到状态转移 公式 dp[i][j] = dp[i+1][j-1] 这时候我们写代码的时候想到的是

// dp[i][j] 表示 s[i..j] 是否是回文串
    boolean[][] dp = new boolean[len][len]; 

    char[] charArray = s.toCharArray();  
    for(int i = 0; i< length; i++) {
        for(int j = 0; j< i; i++) {
            if(charArray[i] == charArray[j]) {
                dp[i][j] =  dp[i+1][j-1]
            }
        }
    }

以上代码在双重循环两个指针,有个问题dp[i][j] = dp[i+1][j-1],因为i是递增的,所以dp[i] 无法利用到 dp[i+1],所以这样写是行不通的

状态转移的过程 就是要从有的状态上去转移,在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。

    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);
}

这里最外层循环是按照字符串长度来循环的举个例子:

dp[1][5] = dp[2][4]表示长度为5的回文串是从长度为3的回文串中得到的 (因为 dp[2][4] 在 L=3的时候已经计算过了),符合动态规划的状态转移过程