class Solution {
public int fib(int n) {
if(n==0) {
return 0;
}
if(n==1) {
return 1;
}
int dp[]=new int[n+1];
//初始化
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++) {
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
class Solution {
public int climbStairs(int n) {
if(n==1) {
return 1;
}
if(n==2) {
return 2;
}
int []dp=new int[n+1];
//初始化
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++) {
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
public int minCostClimbingStairs(int[] cost) {
//定义dp数组含义
int dp[]=new int[cost.length+1];
//初始化
dp[0]=0;
dp[1]=0;
for(int i=2;i<=cost.length;i++) {
dp[i]=Math.min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]);
}
System.out.println(Arrays.toString(dp));
return dp[cost.length];
}
public int uniquePaths(int m, int n) {
int dp[][]=new int[m][n];//到i,j这个位置有dp[i-1][j-1]跳路径
//初始化
for(int i=0;i<m;i++) {
dp[i][0]=1;
}
for(int j=0;j<n;j++) {
dp[0][j]=1;
}
//递推公式
for(int i=1;i<m;i++) {
for(int j=1;j<n;j++) {
//不是从上边走过来就是从右边走过来
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
for里边的循环条件,如果到某一部终止的话,是会直接终止的,不会继续走到下一步。
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//定义dp数组
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int dp[][]=new int[m][n];
//初始化,当遇到障碍物时,后边的直接不初始化了
for(int i=0;i<m&&obstacleGrid[i][0]==0;i++) {
dp[i][0]=1;
}
for(int j=0;j<n&&obstacleGrid[0][j]==0;j++) {
dp[0][j]=1;
}
//递推公式
for(int i=1;i<m;i++) {
for(int j=1;j<n;j++) {
if(obstacleGrid[i][j]==0) {
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
class Solution {
public int integerBreak(int n) {
int dp[]=new int[n+1];//定义dp数组就是第i个能拆分出的最大乘积数
//初始化
dp[0]=0;
dp[1]=1;
dp[2]=1;
for(int i=3;i<=n;i++) {
for(int j=1;j<=i-j;j++) {
dp[i]=Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
}
}
System.out.println(Arrays.toString(dp));
return dp[n];
}
}
//二维背包问题是用上边的和左上角的推出来的,然后层层的去更新数值。
//一维背包是因为要用到左上角的值,所以不能给它覆盖了,所以一位数组要从后往前遍历。要不然就用到本行的数据了。
class Solution {
public boolean canPartition(int[] nums) {
int sum=0;//记录数组中元素的总和
for(int i:nums) {
sum+=i;
}
if(sum%2!=0) {//如果背包元素的和是奇数的话,根本不能分成两个
return false;
}
int dp[]=new int[sum/2+1];//分配一个这样大小的背包,如果能正好把物品装满,那恰好能使物品分成两部分
for(int i=0;i<nums.length;i++) {//先遍历物品
for(int j=sum/2;j>=nums[i];j--) {//再遍历背包
dp[j]=Math.max(dp[j], dp[j-nums[i]]+nums[i]);
}
}
return dp[sum/2]==sum/2;
}
}
public int lastStoneWeightII(int[] stones) {
//先求出石块的总和,然后用背包尽可能的装一半,然后用剩下的减去背包里的就是最小的重量了
int sum=0;
for(int i:stones) {
sum+=i;
}
int target=sum/2;
int []dp=new int[target+1];
for(int i=0;i<stones.length;i++) {//先遍历物品
for(int j=target;j>=stones[i];j--) {
dp[j]=Math.max(dp[j], dp[j-stones[i]]+stones[i]);
}
}
return sum-dp[target]-dp[target];
}
//有dp[1]了,还需要dp[4]种方法去填满这个背包,
//dp[5]=dp[1]+dp[2]+dp[3]+dp[4]
,商品需要去找存在的东西。
public int findTargetSumWays(int[] nums, int target) {
//先求出左边的和的公式,target=(sum+target)/2,然后再求有多少种方法能装满这个背包
int sum=0;
for(int i:nums) {
sum+=i;
}
int temp=(sum+target)/2;
int []dp=new int[temp+1];
for(int i=0;i<nums.length;i++) {//先遍历物品
for(int j=temp;j>=dp[i];j--) {
dp[j]+=dp[j-temp];
}
}
System.out.println(Arrays.toString(dp));
return dp[temp];
}
用两个维度去装背包,先遍历物品再遍历背包,问装满这个背包最多有多少个物品。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int [][]dp=new int[m+1][n+1];//该背包最多有m+1个容量的0和n+1个容量的1,问要装满他最多有多少个子集的长度
for(String str:strs) {
int zeronums=0;
int onenums=0;
//算出每个物品的含量
for(char c:str.toCharArray()) {
if(c=='0') {
zeronums++;
}else if(c=='1') {
onenums++;
}
}
for(int i=m;i>=zeronums;i-- ) {
for(int j=n;j>=onenums;j--) {
dp[i][j]=Math.max(dp[i-zeronums][j-onenums]+1, dp[i][j]);
}
}
}
return dp[m][n];
}
}
完全背包问题:遍历背包时要从小到大去遍历。
有对应的物品的话,组合次数需要去累加。
class Solution {
public int change(int amount, int[] coins) {
int []dp=new int[amount+1];//有一个容量这么大的背包
dp[0]=1;//当背包容量是0的话会有一种情况,就是什么也不装
for(int i=0;i<coins.length;i++) {//先遍历物品
for(int j=coins[i];j<=amount;j++) {//再遍历背包,完全背包是从小到大去遍历
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
}
要是求排列的话,先遍历背包,再遍历物品,求出来的就是排列数。
public int change(int amount, int[] coins) {
//递推表达式
int[] dp = new int[amount + 1];
//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装
dp[0] = 1;
for (int i = 0; i < coins.length; i++) {
for (int j = coins[i]; j <= amount; j++) {//从小到大去遍历时完全背包的问题
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
依次去迭代去找最小的一个。dp[j-coins[i]]+1,和dp[j]中取出最小的一个。
class Solution {
public int coinChange(int[] coins, int amount) {
int []dp=new int[amount+1];
//初始化
dp[0]=0;
for(int i=1;i<=amount;i++) {
dp[i]=Integer.MAX_VALUE;
}
//先遍历物品
for(int i=0;i<coins.length;i++) {
//再遍历背包
for(int j=coins[i];j<=amount;j++) {
if(dp[j-coins[i]]!=Integer.MAX_VALUE) {
dp[j]=Math.min(dp[j], dp[j-coins[i]]+1);
}
}
}
return dp[amount]==Integer.MAX_VALUE?-1:dp[amount];
}
}
class Solution {
public int numSquares(int n) {
//定义dp数组
int []dp=new int[n+1];
//初始化
for(int i=0;i<=n;i++) {
dp[i]=Integer.MAX_VALUE;
}
dp[0]=0;
//遍历顺序
for(int i=1;i*i<=n;i++) {//先遍历物品
for(int j=i*i;j<=n;j++) {//再遍历背包
if(dp[j-i*i]!=Integer.MAX_VALUE) {
dp[j]=Math.min(dp[j], dp[j-i*i]+1);
}
}
}
return dp[n];
}
}
public boolean wordBreak(String s, List<String> wordDict) {
Set<String>set=new HashSet<String>(wordDict);
//定义背包
boolean[]dp=new boolean[s.length()+1];//判断当前长度的字符串能不能由字典组成
//初始化
dp[0]=true;
for(int i=1;i<=s.length();i++) {//先遍历背包
for(int j=0;j<i&&!dp[i];j++) {//再遍历物品
if(set.contains(s.substring(j,i))&&dp[j]) {//代表这个单词之前的能匹配上并且这个单词也能匹配上
dp[i]=true;
}
}
}
return dp[s.length()];
}
动态规划的思想去求,dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
class Solution {
public int rob(int[] nums) {
int []dp=new int[nums.length];//定义dp数组,dp[i]表示偷到dp[i]所能偷到钱的最大数量
if(nums.length==1) {
return nums[0];
}
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<nums.length;i++) {
dp[i]=Math.max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[nums.length-1];
}
}
class Solution {
public int rob(int[] nums) {
if(nums.length==1) {
return nums[0];
}
return Math.max(robhelp(nums,0,nums.length-2), robhelp(nums,1,nums.length-1));
}
public int robhelp(int[] nums,int start,int end ) {
if(start==end) {
return nums[start];
}
int []dp=new int[nums.length];
dp[start]=nums[start];
dp[start+1]=Math.max(nums[start], nums[start+1]);
for(int i=start+2;i<=end;i++) {
dp[i]=Math.max(dp[i-2]+nums[i], dp[i-1]);
}
return dp[end];
}
}
思路:解耦成两个函数,然后分别去找。
思路:
定义好dp数组的含义,然后用动规去找从哪天买入代价最小,然后再找从哪天卖出利益最大。
class Solution {
public int maxProfit(int[] prices) {
int dp[][]=new int[prices.length][2];//dp[i][0]表示持有股票的最大钱数,dp[i][1]表示不持有股票的最大钱数
//初始化
dp[0][0]=-prices[0];
dp[0][1]=0;//不持有的金钱就是0
//递推公式
for(int i=1;i<prices.length;i++) {
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);//决定在哪天买入
dp[i][1]=Math.max(dp[i-1][1], dp[i-1][0]+prices[i]);//决定在哪天卖出
}
return dp[prices.length-1][1];
}
}
和第一次的区别就是这次可以买卖好多次了,买股票时的余额就不是0了,而是之前不持有股票的利益。
class Solution {
public int maxProfit(int[] prices) {
int dp[][]=new int[prices.length][2];//dp[i][0]表示持有股票的最大钱数,dp[i][1]表示不持有股票的最大钱数
//初始化
dp[0][0]=-prices[0];
dp[0][1]=0;//不持有的金钱就是0
//递推公式
for(int i=1;i<prices.length;i++) {
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);//决定在哪天买入
dp[i][1]=Math.max(dp[i-1][1], dp[i-1][0]+prices[i]);//决定在哪天卖出
}
return dp[prices.length-1][1];
}
}
只需要定义好四个状态,然后每个状态去相应的模拟,每个状态都是由上一个状态推出来的,去模拟就好了。
class Solution {
public int maxProfit(int[] prices) {
//dp数组的含义
//dp[i][0] 不做操作的状态
//dp[i][1]第一次持有的状态
//dp[i][2]第一次不持有的状态
//dp[i][3]第二次持有的状态
//dp[i][4]第二次不持有的状态
//初始化
int[][]dp=new int[prices.length][5];
dp[0][0]=0;
dp[0][1]=-prices[0];
dp[0][2]=0;
dp[0][3]=-prices[0];
dp[0][4]=0;
//遍历顺序
for(int i=1;i<prices.length;i++) {
dp[i][1]=Math.max(dp[i-1][1], -prices[i]);
dp[i][2]=Math.max(dp[i-1][2], dp[i][1]+prices[i]);
dp[i][3]=Math.max(dp[i-1][3], dp[i][2]-prices[i]);
dp[i][4]=Math.max(dp[i-1][4], dp[i][3]+prices[i]);
}
return dp[prices.length-1][4];
}
}
也是奇数状态进行初始化为负数,然后遍历出各个物品的每个状态。
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices.length == 0) return 0;
int [][]dp =new int[prices.length][2*k+1];//定义dp数组状态是2k+1个状态
//初始化,当第奇数状态的时候是不持有的状态
for(int i=1;i<k*2;i+=2) {
dp[0][i]=-prices[0];
}
//递推公式,从第一天开始递推,然后有k个状态都要表示出来
for(int i=1;i<prices.length;i++) {
for(int j=0;j<2*k-1;j+=2) {
dp[i][j+1]=Math.max(dp[i-1][j+1], dp[i-1][j]-prices[i]);
dp[i][j+2]=Math.max(dp[i-1][j+2], dp[i-1][j+1]+prices[i]);
}
}
return dp[prices.length-1][2*k];
}
}
思路:将每个状态都列举出来,然后每次去递推,看看哪个状态可以由前边一天的状态推出来。
public int maxProfit(int[] prices) {
int [][]dp=new int[prices.length][4];
/**
* dp数组含义
* dp[i][0]持有股票的状态
* dp[i][1]保持卖出股票的状态,在冷冻期之后,每天都是保持卖出股票的状态
* dp[i][2]当天卖出股票的状态
* dp[i][3]冷冻期
*/
//初始化
dp[0][0]=-prices[0];
dp[0][1]=0;
//递推公式
for(int i=1;i<prices.length;i++) {
dp[i][0]=Math.max(dp[i-1][0], Math.max(dp[i-1][3]-prices[i], dp[i-1][1]-prices[i]));
dp[i][1]=Math.max(dp[i-1][1], dp[i-1][3]);//冷冻期的下一天也是保持卖出股票的状态
dp[i][2]=dp[i-1][0]+prices[i];
dp[i][3]=dp[i-1][2];
}
return Math.max(dp[prices.length-1][1], Math.max(dp[prices.length-1][2], dp[prices.length-1][3]));
}
//只需要在卖出股票的时候把手续费减去就可以了
class Solution {
public int maxProfit(int[] prices, int fee) {
//定义dp数组
int dp[][]=new int[prices.length][2];
//dp[i][0]持有股票的最大利润
//dp[i][1]不持有股票的最大利润
//初始化
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i<prices.length;i++) {
dp[i][0]=Math.max(dp[i-1][0], dp[i-1][1]-prices[i]);
dp[i][1]=Math.max(dp[i-1][1], dp[i-1][0]+prices[i]-fee);
}
return dp[prices.length-1][1];
}
}
从i开始,每次遍历一遍找到比i小的前边的最长递增子序列,然后dp[j]+1即可。
class Solution {
public int lengthOfLIS(int[] nums) {
int []dp=new int[nums.length];//dp[i]表示以i为结尾的字母最长递增子序列的长度是多少
dp[0]=1;
Arrays.fill(dp, 1);
for(int i=1;i<nums.length;i++) {//外边是一套循环,然后里边是第二层循环
for(int j=0;j<i;j++) {//再遍历一次找到i前边的字符串
if(nums[i]>nums[j]) {
dp[i]=Math.max(dp[j]+1, dp[i]);
}
}
}
//然后在里边找到这些数组的最大值
int result=0;
for(int i:dp) {
if(i>result) {
result=i;
}
}
System.out.println(Arrays.toString(dp));
return result;
}
}
public int findLengthOfLCIS(int[] nums) {
int []dp=new int[nums.length];//定义dp数组,dp[i]以i为结尾的数组最长连续递增子序列的长度
//初始化
dp[0]=1;
for(int i=1;i<nums.length;i++) {
if(nums[i]>nums[i-1]) {
dp[i]=dp[i-1]+1;
}else {
dp[i]=1;//从头开始计数
}
}
//找到dp数组里的最大值
int result=0;
for(int i:dp) {
if(i>result) {
result=i;
}
}
System.out.println(Arrays.toString(dp));
return result;
}
//定义好dp数组,然后挨个去遍历,找到相同的字母要在这个基础上+1
public int findLength(int[] nums1, int[] nums2) {
//定义dp数组 ,dp[i][j]表示nums1以i-1为结尾的字符串和nums2以j-1为结尾的字符串最长重复子数组的个数
int [][]dp=new int[nums1.length+1][nums2.length+1];
int result=0;
//初始化
dp[0][0]=0;
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;//代表遇到两个字母相同的话,这个最长重复子数组的长度在之前的基础上+1
}
result=Math.max(result, dp[i][j]);
}
}
return result;
}
只需要考虑两个字母不相等的情况即可。
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int [][]dp=new int[text1.length()+1][text2.length()+1];//dp[i][j]表示text1以i-1为结尾,text2以i-2为结尾的最长重复子数组的长度
dp[0][0]=0;
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()];//因为每次都更新一个值,所以不用更新到最后
}
}
还是求最长公共子序列
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int [][] dp=new int[nums1.length+1][nums2.length+1];//定义dp数组,dp[i][j]表示以nums1以i-1为结尾,text2以i-2为结尾的最长重复子数组的长度
dp[0][0]=0;
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];
}
}
//要考虑 result也和dp[0]比较一下。
class Solution {
public int maxSubArray(int[] nums) {
int []dp=new int[nums.length];//dp[i]表示以下标i结尾的最大子序和的数组
//初始化
if(nums.length==1) {
return nums[0];
}
dp[0]=nums[0];
int result=Integer.MIN_VALUE;//存储最大值
result=Math.max(result, dp[0]);
for(int i=1;i<nums.length;i++) {
dp[i]=Math.max(dp[i-1]+nums[i], nums[i]);
result=Math.max(result, dp[i]);
}
return result;
}
}
public boolean isSubsequence(String s, String t) {
int [][]dp=new int[s.length()+1][t.length()+1];//定义dp[][]数组求出两个字符串的最长公共子序列,然后判断这两个序列是否相等
dp[0][0]=0;
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();
}
public int numDistinct(String s, String t) {
int dp[][]=new int[s.length()+1][t.length()+1];//dp[i][j]表示s以i-1为结尾的序列中有几个t以j-1结尾的子序列
//初始化
dp[0][0]=1;
dp[0][1]=0;
dp[1][0]=1;//前边有一个字符的情况肯定包含第二个字符串没有字符的情况。
for (int i = 0; i < s.length() + 1; i++) {
dp[i][0] = 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)) {//当字符串里下标为i-1和j-1的字符相等时,就考虑它们前一个字母的情况了
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];//即不考虑这个字母的情况和不考虑s中的i-1的字母的情况
}else {
dp[i][j]=dp[i-1][j];//当两个字母不相等的时候就等于前一个字母的情况。
}
}
}
return dp[s.length()][t.length()];
}
class Solution {
public int minDistance(String word1, String word2) {
int dp[][]=new int[word1.length()+1][word2.length()+1];//定义dp数组dp[i][j]表示word1以i-1结尾的和word2以j-1结尾的字符串要达到相同的长度最少删除几个字符串
//初始化
for(int i=0;i<=word1.length();i++) {
dp[i][0]=i;
}
for(int j=0;j<=word2.length();j++) {
dp[0][j]=j;
}
//递推公式
for(int i=1;i<=word1.length();i++) {
for(int j=1;j<=word2.length();j++) {
if(word1.charAt(i-1)==word2.charAt(j-1)) {//如果两个字母相同的话,dp数组就等于它们的上一个字母
dp[i][j]=dp[i-1][j-1];
}else {//如果两个字母不相同的话,就在删第一个字符串的一个字母,删第二个字符串的一个字母,两个字符串的字母都删了,这三种情况中选取一个
dp[i][j]=Math.min(dp[i-1][j]+1, Math.min(dp[i][j-1]+1, dp[i-1][j-1]+2));
}
}
}
return dp[word1.length()][word2.length()];
}
}
//如果两个字母相同的话,就找它对应的前一个,如果不相同的话,就在删上一个字母、删下一个字母、两个字母都删了这两种情况中选一个。
class Solution {
public int minDistance(String word1, String word2) {
int dp[][]=new int[word1.length()+1][word2.length()+1];//定义dp数组dp[i][j]表示word1以i-1结尾的和word2以j-1结尾的字符串要达到相同的长度最少删除几个字符串
//初始化
for(int i=0;i<=word1.length();i++) {
dp[i][0]=i;
}
for(int j=0;j<=word2.length();j++) {
dp[0][j]=j;
}
//递推公式
for(int i=1;i<=word1.length();i++) {
for(int j=1;j<=word2.length();j++) {
if(word1.charAt(i-1)==word2.charAt(j-1)) {//如果两个字母相同的话,dp数组就等于它们的上一个字母
dp[i][j]=dp[i-1][j-1];
}else {//如果两个字母不相同的话,就在删第一个字符串的一个字母,删第二个字符串的一个字母,两个字符串的字母都删了,这三种情况中选取一个
dp[i][j]=Math.min(dp[i-1][j]+1, Math.min(dp[i][j-1]+1, dp[i-1][j-1]+1));//第三种情况就是一个替换的操作,所以就+1,而不是加2了
}
}
}
return dp[word1.length()][word2.length()];
}
}
class Solution {
public int countSubstrings(String s) {
boolean [][]dp=new boolean [s.length()][s.length()];//定义dp数组,dp[i][j]表示以[i,j]为下标的字符串是不是回文子串
//初始化,要初始化为false。
//遍历顺序,因为dp[i][j]要从dp[i+1][j-1]推出来,所以要从左下角到右上角遍历
int result=0;//记录回文字符串的总数
for(int i=s.length()-1;i>=0;i--) {
for(int j=i;j<s.length();j++) {
if(s.charAt(i)==s.charAt(j)) {//当遇到的两个指针相等了
if(j-i<=1) {//这个肯定是回文字符串
dp[i][j]=true;
result++;
}else if(dp[i+1][j-1]){//如果它里边的是一个回文串,那么当两个字母相等的时候它才是回文串
dp[i][j]=true;
result++;
}
}//两个字母不相等的时候默认为false
}
}
return result;
}
}
//p判断两个字母相等的时候+2,不相等的时候减去其中一个字母就行了。
public int longestPalindromeSubseq(String s) {
int dp[][]=new int [s.length()][s.length()];//dp[i][j]表示以i为开头,以j为结尾的字符串最长回文子串
//初始化
for(int i=0;i<s.length();i++) {
dp[i][i]=1;
}
//遍历顺序,从左下角遍历到右上角
for(int i=s.length()-1;i>=0;i--) {
for(int j=i+1;j<s.length();j++) {
if(s.charAt(i)==s.charAt(j)) {//如果两个字符相等的话,证明这就是一个回文子串,然后在这个回文子串的基础上+2
dp[i][j]=dp[i+1][j-1]+2;
}else {//不是回文子串,然后在去掉首尾字符的两种情况下-1
dp[i][j]=Math.max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][s.length()-1];
}