1.最长公共子序列(力扣1143)
本题重点在于状态的转移,以dp[i][j]表示在第一个字符串中下标为i的字符前和在第二个字符串中下标为j的字符前最长的公共子序列长度。若当前两字符相等,有dp[i][j] = dp[i-1][j-1]+1,若不相等,有dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1])。
public int longestCommonSubsequence(String text1, String text2) { int[][] dp = new int[text1.length()+1][text2.length()+1]; for (int i = 1; i <= text1.length(); i++) { for (int j = 1; j <= text2.length(); j++) { //注意这里状态转移,三个方向,如果两字符相等,取前一行前一列的数,若不等取前一行这一列和这一行前一列的最大值 if(text1.charAt(i-1) == text2.charAt(j-1)) dp[i][j] = dp[i-1][j-1]+1; else dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); } } return dp[text1.length()][text2.length()]; }
2.不相交的线(力扣1035)
本题与上题可以说一模一样,都是寻找最长公共子序列,但是这道题要与力扣上第718题区别开,力扣上第718题要求最长重复(公共)子数组,是要求连续的,本题可以不连续,故状态转移时不一样。对于本题:
if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1]+1; else dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
对于力扣上第718题:
if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
并且在收集结果时,本题直接返回dp[nums1.length][nums2.length],718题要返回过程中最大的dp[i][j]。
public int maxUncrossedLines(int[] nums1, int[] nums2) { int[][] dp = new int[nums1.length+1][nums2.length+1]; for (int i = 1; i <= nums1.length; i++) { for (int j = 1; j <= nums2.length; j++) { if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1]+1; else dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); } } return dp[nums1.length][nums2.length]; }
3.最大子数组和(力扣53)
之前在学习贪心时已经做过这道题,这次再遇到就用贪心和DP两个方法做。
public int maxSubArray(int[] nums) { // //贪心 // int sum = 0; // int max = Integer.MIN_VALUE; // for (int i = 0; i < nums.length; i++) { // sum += nums[i]; // if(sum > max) // max = sum; // //负数只会拖累结果 // if(sum < 0) // sum = 0; // } // return max; //DP,dp[i]记录到第i个位置结束时的最大和 int[] dp = new int[nums.length]; dp[0] = nums[0]; //避免从第一个数开始就是一个全部都是负数的递减序列,让最大值是第一个数 int max = nums[0]; for (int i = 1; i < nums.length; i++) { if (dp[i-1] > 0) dp[i] = dp[i-1]+nums[i]; else dp[i] = nums[i]; if(dp[i] > max) max = dp[i]; } return max; }
4.判断子序列(力扣392)
本题可以通过双指针循环一次得到结果,也可以用动态规划判断最长子序列的长度是否等于子序列长度。
public boolean isSubsequence(String s, String t) { // //DP解法 // int[][] dp = new int[s.length()+1][t.length()+1]; // for (int i = 1; i <= s.length(); i++) { // for (int j = 1; j <= t.length(); j++) { // if(s.charAt(i-1) == t.charAt(j-1)) // dp[i][j] = dp[i-1][j-1] + 1; // else // dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]); // } // } // return dp[s.length()][t.length()] == s.length(); //双指针解法 int left = 0; int right = 0; while (left != s.length() && right != t.length()) { if(s.charAt(left) == t.charAt(right)) { left++; right++; } else right++; } return left == s.length(); } //DP解法2,就是两比较元素若不相等则只用等于这一行的前一个 // int[][] dp = new int[s.length()+1][t.length()+1]; // for (int i = 1; i <= s.length(); i++) { // for (int j = 1; j <= t.length(); j++) { // if(s.charAt(i-1) == t.charAt(j-1)) // dp[i][j] = dp[i-1][j-1] + 1; // else // dp[i][j] = dp[i][j-1]; // } // } // return dp[s.length()][t.length()] == s.length(); // }
*5.回文子串(力扣647)
首先用暴力求解,对于每个子串都遍历一次,若是回文子串则res++。然后用中心扩散法,此方法难点在于作为中心的节点会有2*s.length()-1个(包括s.length个单独节点和s.length-1个相邻的两个节点)这样就保证了遇到ababa时以任何单一节点为中心都无法检测子串abab是否为回文串的尴尬,以ba为中心就可以向两边扩展得到abab,故以2*s.length()-1个节点为中心向两边扩展,遇到回文串时res++,已经不是回文串时直接跳出该次循环,遍历下一个中心。
// //暴力解法 // public int countSubstrings(String s) { // int res = 0; // for (int i = 0; i < s.length(); i++) { // for (int j = i+1; j < s.length()+1; j++) { // if(isHuiWen(s.substring(i,j))) // res++; // } // } // return res; // } // public boolean isHuiWen(String s){ // if(s.length() == 0) // return true; // for (int i = 0,j = s.length()-1; i < j; i++,j--) { // if(s.charAt(i) != s.charAt(j)) // return false; // } // return true; // } //双指针法,共有2*s.length-1个中心,从中心向两边扩散,记录回文子串数目 public int countSubstrings(String s){ int res = 0; int left = 0; int right = 0; for (int i = 0; i < 2 * s.length() - 1; i++) { //保证一共2*s.length-1个中心都能遍历到 left = i / 2; right = left + i % 2; while (left >= 0 && right <= s.length()-1){ if(s.charAt(left) == s.charAt(right)){ res++; left--; right++; } //已经不是回文了 else break; } } return res; }