1. /**
    2. * 可以采用样本对应模型和范围尝试模型
    3. * 方式1:样本对应模型:
    4. * 基于上个题目 “求两个字符串的最长子序列长度”
    5. * 一个字符串和与其逆序串 的最长公共子序列长度
    6. * 即为一个字符串的最长回文子序列长度
    7. * 所以:longestCommonSubsequence(str, reverse);即为结果
    8. * 方式2:范围尝试模型:
    9. * L-R 上的最长回文子序列长度
    10. * 尝试讨论LR的情况
    11. * 最长回文子序列长度
    12. */
    13. // 测试链接:https://leetcode.com/problems/longest-palindromic-subsequence/
    14. public class Code01_PalindromeSubsequence {
    15. // 方式2:基于范围尝试模型 暴力递归
    16. public static int lpsl1(String s) {
    17. if (s == null || s.length() == 0) {
    18. return 0;
    19. }
    20. char[] str = s.toCharArray();
    21. return process(str, 0, str.length - 1);
    22. }
    23. // str[L..R]最长回文子序列长度返回
    24. public static int process(char[] str, int L, int R) {
    25. // 讨论边界条件
    26. if (L == R) { // 只有一个字符
    27. return 1;
    28. }
    29. if (L == R - 1) { // 只有两个字符
    30. return str[L] == str[R] ? 2 : 1;
    31. }
    32. // L-R上最长回文子序列的可能性
    33. // 可能性1:最长回文子序列既不以L开头,也不以R结尾, eg: a12321b x L x R
    34. int p1 = process(str, L + 1, R - 1);
    35. // 可能性2:最长回文子序列以L开头,不以R结尾, eg: 12321b √ L x R
    36. int p2 = process(str, L, R - 1);
    37. // 可能性3:最长回文子序列不以L开头,以R结尾, eg: a12321 x L √ R
    38. int p3 = process(str, L + 1, R);
    39. // 可能性4:最长回文子序列以L开头,以R结尾, eg: a123er21a √ L √ R
    40. int p4 = str[L] != str[R] ? 0 : (2 + process(str, L + 1, R - 1));
    41. return Math.max(Math.max(p1, p2), Math.max(p3, p4));
    42. }
    43. // 方式1:暴力递归-》动态规划
    44. public static int lpsl2(String s) {
    45. if (s == null || s.length() == 0) {
    46. return 0;
    47. }
    48. char[] str = s.toCharArray();
    49. int N = str.length;
    50. int[][] dp = new int[N][N];
    51. // 先填写右下角的
    52. dp[N - 1][N - 1] = 1;
    53. // 填写对角线部分
    54. for (int i = 0; i < N - 1; i++) {
    55. // 对角线
    56. dp[i][i] = 1;
    57. // 第二条对角线
    58. dp[i][i + 1] = str[i] == str[i + 1] ? 2 : 1;
    59. }
    60. // 由于剩下的某个点,分别依赖,左边的,下边的,和左下角的三个位置。 从下向上依次填写。N-3开始,以为 N-1和N-2都填写过了
    61. for (int L = N - 3; L >= 0; L--) {
    62. for (int R = L + 2; R < N; R++) { // 从L+2位置开始,前两条对角线L和L+1已经填写过了
    63. // 根据递归过程获取位置依赖
    64. // int p1 = dp[L + 1][R - 1];
    65. // int p2 = dp[L][R - 1];
    66. // int p3 = dp[L + 1][R];
    67. // int p4 = str[L] != str[R] ? 0 : 2 + dp[L + 1][R - 1];
    68. // dp[L][R] = Math.max(Math.max(p1,p2),Math.max(p3,p4));
    69. // 位置优化版本 ?位置依赖 它的左位置,下位置,以及左下位置。三者取最大值,向前推,?左位置怎么求出来的,也是根据这三个位置,
    70. // 中最大值求出来的,下位置同理。因此,?位置,只需要依赖左位置和下位置即可,左位置和下位置一定比左下位置大,因此只需要左位置和
    71. // 和下位置进行比较求最大值即可。于是优化如下:
    72. dp[L][R] = Math.max(dp[L][R - 1], dp[L + 1][R]);
    73. if (str[L] == str[R]) {
    74. dp[L][R] = Math.max(dp[L][R], 2 + dp[L + 1][R - 1]);
    75. }
    76. }
    77. }
    78. return dp[0][N - 1];
    79. }
    80. //#######################################################################################3
    81. // 方式1: 采用样本对应模型,基于最长公共子序列题
    82. public static int longestPalindromeSubseq1(String s) {
    83. if (s == null || s.length() == 0) {
    84. return 0;
    85. }
    86. if (s.length() == 1) {
    87. return 1;
    88. }
    89. char[] str = s.toCharArray();
    90. char[] reverse = reverse(str);
    91. return longestCommonSubsequence(str, reverse);
    92. }
    93. // 最长公共子序列
    94. public static int longestCommonSubsequence(char[] str1, char[] str2) {
    95. int N = str1.length;
    96. int M = str2.length;
    97. int[][] dp = new int[N][M];
    98. dp[0][0] = str1[0] == str2[0] ? 1 : 0;
    99. for (int i = 1; i < N; i++) {
    100. dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
    101. }
    102. for (int j = 1; j < M; j++) {
    103. dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1];
    104. }
    105. for (int i = 1; i < N; i++) {
    106. for (int j = 1; j < M; j++) {
    107. dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
    108. if (str1[i] == str2[j]) {
    109. dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
    110. }
    111. }
    112. }
    113. return dp[N - 1][M - 1];
    114. }
    115. // 字符串反转
    116. public static char[] reverse(char[] str) {
    117. int N = str.length;
    118. char[] reverse = new char[str.length];
    119. for (int i = 0; i < str.length; i++) {
    120. reverse[--N] = str[i];
    121. }
    122. return reverse;
    123. }
    124. //################################################################
    125. // 绝对位置依赖,优化
    126. public static int longestPalindromeSubseq2(String s) {
    127. if (s == null || s.length() == 0) {
    128. return 0;
    129. }
    130. if (s.length() == 1) {
    131. return 1;
    132. }
    133. char[] str = s.toCharArray();
    134. int N = str.length;
    135. int[][] dp = new int[N][N];
    136. dp[N - 1][N - 1] = 1;
    137. for (int i = 0; i < N - 1; i++) {
    138. dp[i][i] = 1;
    139. dp[i][i + 1] = str[i] == str[i + 1] ? 2 : 1;
    140. }
    141. for (int i = N - 3; i >= 0; i--) {
    142. for (int j = i + 2; j < N; j++) {
    143. dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
    144. if (str[i] == str[j]) {
    145. dp[i][j] = Math.max(dp[i][j], dp[i + 1][j - 1] + 2);
    146. }
    147. }
    148. }
    149. return dp[0][N - 1];
    150. }
    151. }