动态规划---子序列问题

一)最长递增子序列:

300. 最长递增子序列 - 力扣(LeetCode)

算法原理:

子序列:任意从数组中挑选出一个元素组成的序列,就叫做子序列,子数组也是子序列的一种情况,子数组必须是连续的

比如说在上面的情况中,所有的数都是面临着选或者是不选,所以一共的情况就是2^N 

严格递增子序列,1 2 3 4,不严格递增子序列: 1 2 3 3 3 4

1.定义一个状态表示:经验+题目要求

上面的这个子序列最后一个元素要么是跟在i-1位置的元素后面构成子序列,要么是跟在i-2位置的元素的后面构成子序列,要么是单独自己一个元素构成子序列

dp[i]表示,以i位置为结尾,最长递增子序列的长度

中心思路就是找到以i位置为结尾的所有递增子序列,然后找到递增子序列中最长的一个子序列

2.根据状态表示推导状态转移方程

像子序列子数组这样的状态转移方程的推导,都是针对于如何构成这个子序列,或者是如何构成这个子数组,来划分问题的,同时以i位置为结尾的递增子序列可以根据长度来进行划分

2.1)单独自己构成一个递增子序列,那么长度就是1

2.2)和前面的元素进行结合构成子序列:要么i位置的元素和i-1位置的元素进行结合成子序列

要么是和i-2位置的元素构成子序列,要么是和i-3位置的元素构成子序列,但是子序列的末尾元素一定要是array[i];

2.3)先找到以j位置为结尾的最长递增子序列的长度,并且array[i]>array[j],然后再加上array[j]的值,就构成了以i为结尾的最长递增子序列

 3.初始化+填表顺序:从左向右

对于长度问题的初始化,一般都是将dp表中的值初始化成元素最差的状态

4.返回值:返回dp表里面的最大值
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp=new int[nums.length];
        int maxlen=Integer.MIN_VALUE;
//1.单独一个元素就可以构成子序列
        for(int i=0;i<dp.length;i++){
            dp[i]=1;
        }
//2.j从0开始,j<=i进行遍历
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                   dp[i]=Math.max(dp[j]+1,dp[i]);//注意一定要是取Math.max
                }
            }
        maxlen=Math.max(dp[i],maxlen);
        }
    return maxlen;
    }
}

二)摆动序列:这个题和之前做的湍流子数组有点像

1)定义一个状态标识:

先找到以i位置元素为结尾的所有子序列中,再找到最长的摆动序列的长度

376. 摆动序列 - 力扣(LeetCode)

2)根据状态表示推导状态转移方程:

当以某一个位置为结尾的时候,最后呈现的趋势可能是下降的趋势也有可能是上升的趋势,所以说如果想以最后一个位置进行研究问题的话,是可以进行细分出两种状态的,要么是以下降状态到达最后一个位置,要么是以上升状态到达最后一个位置

1)f[i]表示以i位置元素为结尾的所有子序列中,最后一个位置呈现上升趋势的最长的摆动序列的长度

2)g[i]表示以i位置元素为结尾的所有子序列中,最后一个位置呈现下降趋势的最长的摆动序列的长度

3)像状态转移方程这类问题也就是子序列问题这样的分析,就是根据子序列的构成来进行分析的,就是来进行分析以i位置元素为结尾的所有子序列是如何来进行构成的

要么是单独自己一个元素来构成子序列,那么这个子序列就是本身,要么就是跟着i元素前面的一个元素后面来充当子序列,长度大于1

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int f[]=new int[nums.length];
        int g[]=new int[nums.length];
        for(int i=0;i<nums.length;i++){
            f[i]=1;
            g[i]=1;
        }
        int maxlen=Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    f[i]=Math.max(f[i],g[j]+1);
                }
                if(nums[i]<nums[j]){
                    g[i]=Math.max(g[i],f[j]+1);
                }
            }
            maxlen=Math.max(f[i],maxlen);
            maxlen=Math.max(g[i],maxlen);
        }
    return maxlen;
    }
}

三)最长递增子序列的个数:

673. 最长递增子序列的个数 - 力扣(LeetCode)

求长度的时候,先将dp标初始化填写最差的次数

先来一个小算法:要求通过一次遍历。找到数组中最大值出现的次数

1)定义一个maxVal和maxCount,一开始的时候先让maxVal等于数组中的第一个元素,maxCount等于1,然后让i下标向后进行遍历

2)如果发现if(maxVal>array[i])说明array[i]绝对不是当前我们要进行寻找的最大值

如果发现maxVal==array[i]那么让计数器++即可

如果发现maxVal<array[i],此时说明array[i]的值更接近于我们想要的答案,此时前面所做的计数全部白费,此时计数器重置,maxVal=array[i],更新最大值并更新最大值出现的次数等于1,重新计数

1.定义一个状态表示

dp[i]表示以i位置元素为结尾的所有子序列中,最长的递增子序列的个数,但是这个状态标识连最长的子序列的长度都不知道

f[i]表示以i位置元素为结尾的所有子序列中,最长的递增子序列的长度

g[i]表示以i位置元素为结尾的所有子序列中,最长的递增子序列的个数

2.根据这个状态表示推导状态状态转移方程

1)if(array[i]>array[j])说明i是可以跟在j的屁股后面的,此时以i位置为结尾的递增子序列的长度就是以j位置为结尾的最长递增子序列的所有元素在加上当前位置的元素,长度就等于f[j]+1,f[i]=Math.max(f[j]+1,f[i])

2)if(f[j]+1==f[i]) g[i]=g[i]+g[j]

if(f[j]+1<f[i]) 直接忽视

if(f[j]+1>f[i]) f[i]=f[j]+1,g[i]=g[j]

 

3.初始化+填表顺序

将两个表都初始化成1,从左向右填表

4.返回值

返回值最大长度对应的个数

class Solution {
    public int findNumberOfLIS(int[] nums) {
    int[] f=new int[nums.length];
    int[] g=new int[nums.length];
    //f[i]表示以i位置为结尾的元素最长递增子序列的长度
    //g[i]表示以i位置为结尾的元素最长递增子序列的个数
//1.初始化
    for(int i=0;i<nums.length;i++){
        f[i]=g[i]=1;//别忘了初始化
    }
//2.填表
    for(int i=0;i<nums.length;i++){
        for(int j=0;j<i;j++){
          if(nums[j]<nums[i]){
            if(f[j]+1==f[i]){
                g[i]=g[i]+g[j];
            }else if(f[j]+1<f[i]){
                continue;
            }else{
                f[i]=f[j]+1;
                g[i]=g[j];
            }
         }
        }
    }
//3.返回值
int max=f[0];
int maxLen=g[0];
System.out.println(Arrays.toString(f));
System.out.println(Arrays.toString(g));
    for(int i=1;i<nums.length;i++){
        if(max==f[i]) maxLen+=g[i];
        else if(max<f[i]){
            maxLen=g[i];
            max=f[i];
        }
    }
 return maxLen;
    }
}

这个题如果说实在是理解不了:先进行想一下如何先进行计算f[i]的值,f[i]是以i位置为结尾最长递增子序列的长度,求出Math.max(f[j]+1)的值即可,然后再最大长度的情况下,求出出现的最大长度出现的个数,和上面我们的小demo是相等的,在小demo中,我们进行寻找最大值,还要寻找最大值出现的次数,而在本题中,我们不光要进行寻找最大的长度,还要进行寻找最大的长度所出现的次数,所以可以依次扫描完成两次工作;

四)最长数对链

646. 最长数对链 - 力扣(LeetCode)

1)定义一个状态表示:

dp[i]表示以i位置为结尾最长的数对链的长度

1)做动态规划问题的时候,一定要保证填表顺序是有序的才行,也就是说你进行填写当前元素的状态的时候,之前的状态必须是已经计算过的才可以,例如子数组和子序列,例如说以某一个位置为结尾进行研究问题的时候,此时dp[i-1]....等等的值一定是已经计算完成的,因为子数组问题一定是连续的,如果我们以i位置的元素为结尾进行研究数对链问题的时候,倒数第二个位置可能是在左边,也可能是在右边,依赖于i-1位置的值,还依赖于i+1位置的值

2)我想进行考虑以i位置元素为结尾考虑数对链的时候,i前面的元素的数对链绝对不可能是i+1位置的数对链i前面的数对链一定不可能是i+1后面的数对链

2.根据状态表示推导状态转移方程 

1)要么是单独i位置元素的数对单独构成一个数对链,要么是和i-1位置的元素的后面构成数对链,要么是和i-2位置的元素的后面构成数对链,要么就跟在0的后面形成数对链

要么单独自己构成数对链,要么和前面的元素相结合构成数对链,要是前面的数对能和当前的数对构成数对链,那么数对链的个数就是前面数对链的个数+1即可

2)if(dp[j][1]<dp[i][0]) dp[i]=dp[j]+1

要想构成数对链,这个数对链的第一个元素必须大于前面数对链的末尾的元素

3.初始化+填表顺序

dp[0]=1,表中元素全部初始化成1

4.返回值:返回表里面的最大值
class Solution {
    public int findLongestChain(int[][] array) {
//1.先针对于二维数组进行排序
        Arrays.sort(array,(a,b)->a[0]-b[0]);
//2.创建dp表
  int[] dp=new int[array.length];
  int max=-1;
  for(int i=0;i<array.length;i++) dp[0]=1;
//3.填写dp表
  for(int i=0;i<array.length;i++){
      for(int j=0;j<i;j++){
          if(array[i][0]>array[j][1]){
              dp[i]=Math.max(dp[i],dp[j]+1);
          }
      }
  max=Math.max(dp[i],max);
  }
  return max;
    }
}

五)最长定差子序列

1218. 最长定差子序列 - 力扣(LeetCode)

1)定义一个状态表示:

根据最近的一步来划分问题

dp[i]表示以i位置为结尾的所有定差子序列中,其中最长的定差子序列的长度

2)根据状态表示推导状态转移方程:

要么是自己单独构成一个子序列,长度是1

要么是和前面的子序列进行组合,长度就是dp[j]+1

当前元素是array[i],那么前面的元素一定是d-array[i],当我们进行研究以i位置为结尾的元素的时候,i位置前面那个元素已经确定了就是,我只要知道最后一个元素就能知道倒数第二个元素

1)如果array[i]-d的元素在i位置元素之前已经出现过了,那么array[i]-d的定长等差序列后面再加上一个array[i]元素即可,那么此时最长等差子序列的长度就是dp[array[i]-d]+1

2)否则自己单独构成一个子序列,在前无法找到值等于array[i]-d的元素

3)因为array[i]-d的值可以进行确定,可以直接在哈希表中做动态规划下面这种写法时间超时

1)如果我们在进行寻找b的值的时候,还是需要从0号位置到j号位置进行寻找,所以时间复杂度就是O(N),但是如果我们把nums[i]的值和dp[i]的值存放到哈希表中,那么进行查找b的值的时候就可以直接通过O(1)的时间复杂度得出结论了将元素将dp[j]的值绑定存放到哈希表中

2)可以直接在哈希表中做动态规划

class Solution {
    public int longestSubsequence(int[] array, int difference) {
        int[] dp=new int[array.length];
        int max=dp[0];
   for(int i=0;i<array.length;i++) dp[i]=1;
        for(int i=1;i<array.length;i++){
            for(int j=0;j<i;j++){
               if(array[i]-array[j]==difference){
                    dp[i]=Math.max(dp[j]+1,dp[i]);
                    break;
                }else{
                    dp[i]=1;
                }
            }
            max=Math.max(max,dp[i]);
        }
    return max+1;
    }
}
class Solution {
    public int longestSubsequence(int[] array, int d) {
        //int[] dp=new int[array.length];
        int max=-1;
        //dp[i]表示以i位置元素为结尾,所有子序列中最长定差子序列的长度
        HashMap<Integer,Integer> map=new HashMap<>();
        //key表示array[i],value表示以i位置为结尾最长定差子序列的长度
        for(int i=0;i<array.length;i++){
             int result=array[i]-d;
             int count=map.getOrDefault(result,0)+1;
             map.put(array[i],count);
             max=Math.max(max,count);
        }
    return max;
    }
}

六)最长斐波那契子序列的长度

873. 最长的斐波那契子序列的长度 - 力扣(LeetCode)

严格递增:不能出现元素相等的元素,不包含相等的元素

1)定义一个状态表示:在本题中是属于严格递增的,每一个数当然是不重复的

1)dp[i]表示以i位置元素为结尾的所有子序列中,最长的斐波那契子序列的长度,之前在做子序列问题的时候,按照思路应该是从0-i-1这些位置选择一个j位置,根据j位置的dp值dp[j]的值来更新dp[i]位置的值但是此时你只能定位到j位置的值和i位置的值,只能知道最长斐波那契额子序列的长度,但是你无法找出斐波那契的子序列,但是前面的元素就是nums[i]-nums[j],所以说根据i位置的值和j位置的值,最前面的那一个元素位置的值是可以计算出来的,但是j位置的值也不确定,所以说无法根据dp[i]位置的值来进行确定整个斐波那契额数列的包含的元素;

2)dp[j]表示以j位置为结尾最长斐波那契额子序列的长度,所以只是知道dp[j]不知道斐波那契长成什么样子,可能是a后面跟上了一个nums[j],也有可能是b后面跟上了一个nums[j],其实假设如果我们知道了最后两个元素,前面的元素我们是可以推出来的

2)根据状态表示推导状态状态转移方程

1)单独一一个元素为结尾是无法推出斐波那契子序列的,但是如果如果是以两个元素为结尾是可以推出斐波那契子序列的,但是假设如果我们知道了斐波那契额数列的最后两个元素,那么前面的元素我们是可以推导出来的

2)dp[i][j]表示以i位置以及j位置的元素为结尾所有的子序列中,最长的斐波那契额子序列的长度,规定i位置元素在前,j位置元素在后;

3)定义i下标的元素是b,j位置的元素是c,那么前一个位置的元素一定是c-b=a

c-b这个数必须在b前面,因为我们规定的是dp[i][j]是以i位置和j位置为结尾,还有虽然以i位置和j位置为结尾,但是i,j位置是具有长度的,所以dp[i][j]要初始化成2

4)如果我们最终计算出来的dp[i][j]的值都是2,那么是无法构成斐波那契额数列的

5)如果c-b存在况且a<b我们此时进行求解的是最长斐波那契额子序列的长度,我先找到以i位置元素和k位置元素为结尾的最长的斐波那契子序列的长度+1即可,dp[i][j]=dp[k][i]+1

优化:我们在图中找到值是a的这个元素的过程中,需要从0位置下标从前向后到i位置开始进行遍历,不光要找到a这个元素还要找到a这个元素所在的下标,找下标是为了推导状态转移方程,所以我们提前进行处理,可以将数组元素以及数组元素所对应的下标存放到哈希表中

3)初始化:

因为dp[i][j]表示的是以i位置和j位置为结尾的所有子序列中,最长的斐波那契额子序列的长度

dp[i][j]表示至少以两个元素为结尾,那么长度至少是2,那么可以在进行初始化的时候,将整个数组的元素全部初始化成2,但是假设dp[0][0]是以0号位置和0号位置为结尾的子序列中,最长的斐波那契额子序列的长度,我们已经规定了i<j,所以是一定会存在两个元素的

在上面这个图中,只会使用到i<j位置的值,i=j以及i>j位置的值是无法使用到的,在下面的这个图只会使用到对角线上面的值;

k<i<j所以dp[i][j]是在dp[k][i]的下面

4)填表顺序+返回值

当进行填写dp[i][j]的时候,一定要保证dp[k][i]的值已经计算出来了,但是dp[k][i]的值一定是在dp[i][j]的值的上面的,所以填表顺序就是从上向下进行填表的,返回值就应该是dp表里面的最大值,但是假设整个数组的元素就是[1,2,4]那么在整个数组中没有斐波那契额子序列

那么dp[i][j]里面的值全部是2,那么最终max就是2,那么如果最大值等于2,那么返回0

class Solution {
    public int lenLongestFibSubseq(int[] array) {
     int[][] dp=new int[array.length][array.length];
     HashMap<Integer,Integer> map=new HashMap<>();
     int max=-Integer.MAX_VALUE;
//1.先进行初始化dp表,以及将数组中的每一个元素以及下标和下标元素存放到哈希表中
     for(int i=0;i<array.length;i++){
         map.put(array[i],i);
         for(int j=0;j<array.length;j++){
             dp[i][j]=2;
         }
     }
//2.进行填表
    for(int j=2;j<array.length;j++){//先固定倒数第一个数
        for(int i=1;i<j;i++){//再进行枚举倒数第二个数
            int indexData=array[j]-array[i];
            int k=map.getOrDefault(indexData,-1);
//得到array[i]-array[j]的下标
            if(k==-1) dp[i][j]=2;
            else if(k>=0&&k<i) {
                dp[i][j]=Math.max(dp[k][i]+1,dp[i][j]);
            }
            else dp[i][j]=2;
            max=Math.max(max,dp[i][j]);
        }
    }
     return max==2?0:max;//考虑数组中没有斐波那契额数列的情况
    }
}

这是上面中我曾经写错的一段代码,这段代码错误的原因就是再进行计算dp[1][2]的时候,那上面的测试用例来进行举例:

nums[0]和nums[1]和nums[2]互为斐波那契额子序列

dp[1][2]=dp[0][1]+1,而dp[0][1]的之没有进行过初始化,所以dp[1][2]=1,所以说这肯定是错误的,因为dp[1][2]=3,下面这个代码就是正确的

七)最长等差子序列:

1027. 最长等差数元素列 - 力扣(LeetCode)

1)确定一个状态表示:

以某一个位置为结尾研究问题,像这种子序列问题

要么是单独自己构成子序列,要么是和前面的某一个元素的子序列构成等差数列

dp[i]表示以i位置为结尾的所有的子序列中,最长的等差子序列的长度

2)根据状态标识推导状态转移方程:

推导的方法:

1)我们以前是根据dp[j]的值来进行推导dp[i]的值,因为最长等差序列的长度,我们的dp[j]只是知道长度,i是否可以跟在j后面,都不知道规则,都不知道以j位置为结尾的最长等差序列是长成什么样子的,i位置的值和j位置的值是否可以构成等差序列都是未知的,是无法导推到状态转移方程的,所以我们的状态标识一定要包含两个信息:

2)或者是说我知道等差数列的最后两项,根据这两项的信息,就可以推出来前面的所有等差数列的信息

3)dp[i][j]表示以i位置元素为结尾以及以j位置为结尾的所有的子序列中最长的等差序列的长度

但是我们进行查找a的时候,还是需要在i位置前面进行查找a元素,还是需要进行遍历i前面的所有元素查找最后一个a元素以及a元素所在的下标所以优化思路就是将所有的元素以及对应的下标存放到哈希表里面,key值是元素本身,val值是这个数所对应的下标(存在问题)

0)当进行求解a的位置的时候,我们只需要进行求解离i位置最近的a的下标即可,但是此时可能会出现很严重的问题,如果说我们在一开始还没有进行填写dp表的时候,直接将所有数以及所有数的下标存放到哈希表里面假设在c后面还有a这个数呢,此时计算的k的下标是一定是在c后面,但是前面也是有a的呀,所以此时因为哈希表错误的存放,dp[i][j]==2

1)在dp之前将所有的元素以及下标数组存放到哈希表中<key,下标数组>,这样我们就可以根据元素快速找到这个元素所对应的下标数组,如果下标数组非常大,时间复杂度仍然很高,如果依旧是遍历这个下标数组,那么可能会超时

2)一边做动态规划,一边保存离i最近的元素下标,就是一边做动态规划,一边保存i位置前面的元素以及下标<元素,下标>

1)先固定最后一个数,枚举倒数第二个数,先固定j位置的这个数,然后枚举i位置的这个数,其实离i最近的元素位置是在不断发生改变的,这样元素的相对位置就不好保证了,i在一直进行移动,那么离i最近的那一个元素的值是在一直进行改变的,因为i位置的值实在不断地进行发生变化的,所以是很难保证离i最近的那一个元素位置的值出现

2)先固定倒数第二个数,再来枚举最后一个数,就是进行先固定i位置的数,然后进行向后枚举j位置的数,因为当我们固定i位置的元素的时候,我们的j是在不断向后进行移动的,我们的i位置的值是固定不动的,我们只需要在哈希表中找到i位置以前的所有元素的下标和对应的元素即可,这样要想找到离i位置最近的那一个元素值存放到哈希表里面即可,将i位置的值以及i位置的元素存放到哈希表里面,然后再将i和j统一向后移动一位

1)最终选择的是第二种存数方式当进行填写完成j和后面的i位置的元素的时候,我们的i和j统一向后移动1步,此时再将array[i]和i的值存放到哈希表中,这样才可以保证在哈希表中能够获取到最近的元素的值离i位置最近的元素的值我们在这里面选择的是第二种填表方式,当i位置的元素填写完成之后,直接就将i位置的元素存放到哈希表中即可,我们就可以保证当我们进行填写完一个位置之后,这个位置向后移动,dp表里面存放的是最近的一个

2)那么我们最终要选择的是哪一种填表策略呢,主要是要能够保证我们一边进行填写dp表的时候,一边要能保证一边进行dp操作,一边能够保存离i最近的元素以及元素下标,

3)初始化+返回值:

1)单独列举两个数的时候也是可以构成等差数列的,所以可以将dp[i][j的值初始化成2,那么为什么dp[0][0]可以初始化成2呢?

2)初始化的时候dp[i][j]=2,在上面这个图中,只会使用到i<j位置的值,i=j以及i>j位置的值是无法使用到的,对角线和对角线下面的元素是使用不到的

3)返回值是,返回dp表里面的最大值即可

class Solution {
    public int longestArithSeqLength(int[] nums) {
        if(nums==null) return -1;
//1.创建一个dp表
    int[][] dp=new int[nums.length][nums.length];
    HashMap<Integer,Integer> map=new HashMap<>();
    int max=2;
    for(int i=0;i<nums.length;i++){
        for(int j=0;j<nums.length;j++){
           dp[i][j]=2;
        }
    }
//dp[i][j]表示以i元素况且以j元素为结尾的所有子序列中,最长等差子序列的长度
    for(int i=0;i<nums.length;i++){
        for(int j=i+1;j<nums.length;j++){
            int data=2*nums[i]-nums[j];
            int index=map.getOrDefault(data,-1);
            if(index==-1){
                dp[i][j]=2;
            }else if(index>=0&&index<i){
                dp[i][j]=dp[index][i]+1;
            }else if(index>=i&&index<=j){
                dp[i][j]=2;
            }
             max=Math.max(dp[i][j],max);
        }
        map.put(nums[i],i);
    }
    return max;
    }
}

八)等差数列划分

446. 等差数列划分 II - 子序列 - 力扣(LeetCode)

1)定义一个状态表示:

dp[i]表示以i位置为结尾的所有子序列中,等差子序列的个数,这个状态标识致命的缺点就是只能进行判断子序列的个数,但是根本是无法进行判断一个具体的子序列,都不能确定i是否能够和j位置的元素能够构成等差子序列

 dp[i][j]表示以i位置的元素以及以j位置的元素为结尾的所有子序列中等差子序列的个数

dp[i][j]+=dp[kx][i]+1 

假设在上面的那种情况dp[i][j]表示以i,j位置的元素为结尾的所有子序列中等差子序列的个数,如果nums[i]*2-nums[j]在i位置前面无法找到,那么此时dp[i][j]的值应该初始化成0,假设现在有1 2 3三个数 dp[0][1]=0,dp[1][2]=dp[0][1]+1=1,那么此时以1和2为结尾的等差子序列个数就是1

我们要找的是以i和j位置为结尾的最长等差子序列的个数,我先找到以kx和i位置为结尾的最长等差子序列的个数,dp[i][j]=dp[k][i],但是之前以dp[kx][i]的状态标识忽略了一种情况是

kx i j这三种情况的组合

dp[i][j]+=dp[kx][i]+1

2)根据状态表示推到状态转移方程:

但是从上面的过程中我们可以发现,我们还是需要从0号位置元素开始向后进行遍历找kx下表,知道遍历到i位置元素为止,时间复杂度会达到O(N^3)

优化:我们要快速找到某一个元素所对应的下标,因此我将所有元素以及下标数组绑定在一起放在哈希表中,因为一个元素是存在很多下标的

3)初始化+填表顺序+返回值:

固定倒数第一个数,枚举倒数第二个数进行填写dp[i][j]

返回值:返回dp表中所有的值的和

package com.example.demo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

class Solution {
    public static int numberOfArithmeticSlices(int[] array) {
        //1.先创建一个dp表
        int[][] dp=new int[array.length][array.length];
        int sum=0;
        //2.初始化hash表,存放的是key是数组元素,存放的value是数组元素所对应的下标集合
        HashMap<Integer, List<Integer>> map=new HashMap<Integer, List<Integer>>();
        for(int i=0;i<array.length;i++){
            if(map.containsKey(array[i])){
                List<Integer> list=map.get(array[i]);
                list.add(i);
            }else{
                List<Integer> list=new ArrayList<>();
                list.add(i);
                map.put(array[i],list);
            }
        }
        //3.进行填表
        for(int j=0;j<array.length;j++){
            for(int i=0;i<j;j++){
                int data=array[i]*2-array[j];//可能data的值会溢出
                List<Integer> list=map.get(data);
                if(list==null) continue;
                else{
                    for(int index:list){
                        if(index<i){
                            dp[i][j]+=dp[index][i]+1;
                        }
                    }
                }
                sum+=dp[i][j];
            }
        }
        return sum;
    }

    public static void main(String[] args) {
        numberOfArithmeticSlices(new int[]{7,7,7,7,7});

            }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最长公共子序列问题(Longest Common Subsequence,简称LCS)是指在两个序列中找到一个最长的公共子序列,其中一个序列的所有元素按原序列中出现的顺序排列,而另一个序列中的元素则不要求按原序列中出现的顺序排列。 动态规划方法可以很好地解决LCS问题。设A和B是两个序列,LCS(A,B)表示A和B的最长公共子序列。则可以设计如下的状态转移方程: 当A和B的末尾元素相同时,LCS(A,B) = LCS(A-1,B-1) + 1。 当A和B的末尾元素不同时,LCS(A,B) = max(LCS(A-1,B), LCS(A,B-1))。 其中,LCS(A-1,B-1)表示A和B的末尾元素相同时的情况,LCS(A-1,B)表示A的最后一个元素不在最长公共子序列中,而B中的最后一个元素在最长公共子序列中的情况,LCS(A,B-1)表示B的最后一个元素不在最长公共子序列中,而A中的最后一个元素在最长公共子序列中的情况。 根据这个状态转移方程,可以使用动态规划算法来求解LCS问题。具体方法是,构建一个二维数组dp,其中dp[i][j]表示A前i个元素和B前j个元素的LCS。初始化dp[0][j]和dp[i][0]为0,然后按照上述状态转移方程进行递推,最终得到dp[lenA][lenB],其中lenA和lenB分别表示A和B的长度。dp[lenA][lenB]即为A和B的最长公共子序列的长度。要找到具体的最长公共子序列,可以从dp[lenA][lenB]开始,按照状态转移方程反向推导出每个元素,即可得到最长公共子序列。 LCS问题动态规划算法的经典应用之一,时间复杂度为O(n*m),其中n和m分别为A和B的长度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值