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








