子序列问题

子序列问题

最长递增自序列

动态规划

状态表示

dpi:以i位置结尾,所有自序列中,最长长度

状态转移方程

长度为1 1

长度大于1 j取值范围 0,i-1,求出 j 结尾中的最长子序列长度+1就是dpi的结果

初始化

此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况

填表顺序 :从左到右

返回值:dp表中最大值

java 复制代码
class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        for (int i = 0; i < n; i++) {
            dp[i] = 1;//最小为1
        }
        int ret = 1;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            //更新结果
            ret = Math.max(ret,dp[i]);
        }
        return ret;
    }
}

摆动序列

题目解析:求最长摆动序列长度,就是两个元素差值是一正一负情况,也就是递增和递减先后出现

状态表示

fi : 以i位置结尾元素子序列中,最后呈现"上升"趋势最长摆动序列长度

gi : 以i位置结尾元素子序列中,最后呈现"下降"趋势最长摆动序列长度

状态转移方程

长度为1 1

长度大于1 j取值范围 0,i-1 ,求出满足摆动序列最大长度

numsj < numsi -> fi = max(fi , gj + 1)

numsj > numsi -> gi = max(gi , fj + 1)

初始化

此时最小长度为1,此时可以将f和g表初始化为1,不用考虑长度为1的情况

填表顺序 :从左到右

返回值:f和g表中最大值

java 复制代码
class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];//结尾是上升
        int[] g = new int[n];//结尾是下降

        for(int i = 0;i < n;i++){
            f[i] = g[i] = 1;
        }
        int ret = 1;
        for(int i = 1;i < n;i++){
            for(int j = 0;j < i;j++){
                if(nums[j] < nums[i]){
                    f[i] = Math.max(f[i],g[j]+1);
                }else if(nums[j] > nums[i]){
                    g[i] = Math.max(g[i],f[j]+1);
                }
            }
            ret = Math.max(Math.max(f[i],g[i]),ret);
        }
        return ret;
    }
}

最长递增子序列的个数

题目解析:找出最长递增子序列的个数,上面已经知道如何找最长子序列了

状态表示

leni : 以i位置结尾元素子序列中,最长递增子序列的"长度"

counti : 以i位置结尾元素子序列中,最长递增子序列的"个数"

状态转移方程

长度为1 1

长度大于1 j取值范围 0,i-1 ,numsi > numsj 找出最长递增子序列长度及其个数

if(lenj+1 == leni){

//更新最大长度的值

counti += countj;

//最长长度发生变化

}else if(lenj + 1 > leni){

leni = lenj+1;

counti = countj;//重新计数

}

初始化

最长递增子序列长度为1,出现此时最长递增子序列个数最小为1,

填表顺序 :从左到右

返回值:需要找出len表中最大长度,并找出最大长度出现次数count表、

java 复制代码
class Solution {
    public int findNumberOfLIS(int[] nums) {
        int n = nums.length;
        int[] len = new int[n];//最长递增子序列长度
        int[] count = new int[n];//最长递增子序列出现的个数

        for(int i = 0;i < n;i++){
            len[i] = count[i] = 1;
        }
        int retlen = 1;
        int retcount = 1;
        for(int i = 1;i < n;i++){
            for(int j = 0;j < i;j++){
                if(nums[j] < nums[i]){
                    //长度一样
                    if(len[j]+1 == len[i]){
                        //更新最大长度的值
                        count[i] += count[j];
                    //最长长度发生变化
                    }else if(len[j] + 1 > len[i]){
                        len[i] = len[j]+1;
                        count[i] = count[j];//重新计数
                    }
                }
            }
            if(retlen == len[i]){
                retcount += count[i];
            }else if(retlen < len[i]){//跟新最长长度,重新计数
                retlen = len[i];
                retcount = count[i];
            }
        }
       
        return retcount;
    }
}

最长数对链

题目解析:最长数对链长度,当一个数对的头大于一个数对的尾,可以将其跟随到尾部,此时这里是任意选择

因为这里任意选择,导致填表需要根据其前后两端进行填表,无法确定,此时可以先根据其数对中第一个元素进行从小到大排序,这样只需要根据当前位置之前的值进行填表即可,其排序后,其前面元素是无法插在当前数对的尾部

动态规划(先排序)

状态表示

dpi:以i位置元素结尾, 最长数对链路长度

状态转移方程

长度为1 1

长度大于1 j取值范围 0,i-1,求出 j 结尾中的dpj+1最大值就是dpi的结果

初始化

此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况

填表顺序 :从左到右

返回值:dp表中最大值

java 复制代码
class Solution {
    public int findLongestChain(int[][] pairs) {
        int n = pairs.length;
        //根据数对中第一个元素进行排序,这样每次只根据前面更新dp表即可
        Arrays.sort(pairs,(a,b) -> a[0] - b[0]);
        int[] dp = new int[n];
        for(int i = 0;i < n;i++){
            dp[i] = 1;
        }
        int ret = 1;
        for(int i = 1;i < n;i++){
            for(int j = 0;j < i;j++){
                //尾 < 头
                if(pairs[j][1] < pairs[i][0]){
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
            ret = Math.max(ret,dp[i]);
        }
        return ret;
    }
}

最长定差子序列

题目解析:找出最长定差子序列长度

动态规划

状态表示

dpi:以i位置元素结尾, 最长定差子序列长度

状态转移方程

当前元素是a

找是否存在 a - difference,没有就是1

找到可能有多个,但是只需要最后一个即可,因为其长度是最长的

初始化

此时最小长度为1,此时可以将dp表初始化为1,不用考虑长度为1的情况

填表顺序 :从左到右

返回值:dp表中最大值

java 复制代码
这里因为其定差是确定的,可以使用哈希表将其arr[i],和 dp[i]进行绑定
其长度为数组中 其key: arr[i]-difference value:长度+1就是arr[i]对应的值
但是这里可能找不到,找不到,自己构成,长度为1
遍历数组,找其arr[i]-difference长度进行更新,数组是从前向后,其到后面结果会覆盖前面,符合最长长度(最后一个是最长)
java 复制代码
class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        //使用哈希表,用其做dp
        Map<Integer,Integer> hash = new HashMap<>(); //arr[i] dp[i]
        int ret = 1;
        for(int a : arr){
            //以arr[i]结尾的定差长度
            //没有就是1
            //这里需要最长的,因此最长的是arr数组中最后一个a-difference == b
            //这里从前向后,相同值会覆盖,所以最终结果就是最长
            hash.put(a,hash.getOrDefault(a-difference,0) + 1);
            ret = Math.max(ret,hash.get(a));
        }
        return ret;
    }
}

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

题目解析:找出最长斐波那契子序列长度,满足斐波那契就是 arri + arri+1 == arri+2,一个序列所有元素都满足

动态规划

状态表示

dpij:以i位置元素及其j位置元素结尾, 最长斐波那契子序列长度(i < j)

状态转移方程

a = arrj - arri找是否存在这个数

存在元素a a < arri dpij = dpki + 1

存在元素a arri <a < arrj - > 2

不存在a 2

初始化

将dp表中都初始化为2,简化状态转移方程填写

填表顺序 :从上到下

返回值 :dp表中最大值

可以将所有元素及其下标对应关系放到哈希表中,这样查找效率高

java 复制代码
class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n = arr.length;
        int[][] dp = new int[n][n];//以i,j结尾最长斐波那契子序列长度
        Map<Integer,Integer> hash = new HashMap<>();
        //将值和下标绑定
        //这里是严格递增不会有重复元素
        for(int i = 0;i < n;i++){
            hash.put(arr[i],i);
        }
        for(int i = 0;i < n;i++){
            for(int j = 0;j < n;j++){
                dp[i][j] = 2;
            }
        }
        int ret = 2;
        for(int j = 2;j < n;j++){//固定最后一个数
            for(int i = 1;i < j;i++){//倒数第二个数
                int x = arr[j] - arr[i];//需要找的值
                //找到这个数,并且其位置是在i下标之前
                if(x < arr[i] && hash.containsKey(x)){
                    dp[i][j] = dp[hash.get(x)][i] + 1;
                    ret = Math.max(ret,dp[i][j]);
                }
            }
        }
        //可能构不成斐波那契子序列
        return ret < 3 ? 0 : ret;
    }
}

最长等差数列

题目解析:求最长等差子序列长度

动态规划

和上题一样,需要知道不仅需要最后一个数也需要倒数第二个数,依旧使用二维数组

dpij:以i位置元素及其j位置元素结尾子序列中, 最长等差序列长度(i < j)

状态转移方程

a = arrj - arri找是否存在这个数,有的话假设下标为k

存在元素a k < i dpij = dpki + 1

存在元素a i <= k <= j 2

不存在a 2

初始化

将dp表中都初始化为2,简化状态转移方程填写

填表顺序

有要求,1.固定倒数第二个数,枚举倒数第一个数,从哈希表中找符合等差的数(为了正确使用哈希表)

返回值 :dp表中最大值

使用一个哈希表

一边dp,一边将最近元素<元素,下标>放入哈希表中

当i位置(倒数第二个数)填写之后,将i位置对应元素和下标放入哈希表中

因为i每次固定,所以这里哈希表中存放就是最近的,并且使用不会错

如果是固定最后一个数,枚举倒数第二个数,其i不断变化,离i倒数第二个数最近元素下标不但变化,会导致错误

java 复制代码
class Solution {
    public int longestArithSeqLength(int[] nums) {
        int n = nums.length;
        int[][] dp = new int[n][n];
        Map<Integer, Integer> hash = new HashMap<>();
        //初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j] = 2;
            }
        }
        hash.put(nums[0],0);
        int ret = 2;
        for (int i = 1; i < n; i++) {//先确定倒数第二个数
            for (int j = i + 1; j < n; j++) {//枚举最后一个数
                int x = 2 * nums[i] - nums[j];
                //哈希表中找出一个满足等差的数
                if(hash.containsKey(x)){
                    dp[i][j] = dp[hash.get(x)][i] + 1;
                    ret = Math.max(ret,dp[i][j]);
                }
                
            }
            //将其放入哈希表中
            hash.put(nums[i],i);   
        }
        return ret;
    }
}

等差数列划分||-子序列

题目解析:求出等差序列的个数

这里不仅要知道等差序列的长度,也要知道等差序列对应的序列,这样后续填表才可以知道是否可以构成等差数列

动态规划

状态表示

dpij:以i位置元素及其j位置元素结尾子序列中, 等差序列的个数(i < j)

状态转移方程

a = 2 * arri - arrj找出所有符合要求k下标,因为都可以构成等差序列,都要加上

存在元素a 所有的符合要求k下标 k < i dpij = dpki + 1

存在元素a 下标不符合要求 0

不存在a 0

初始化

将dp表中都初始化为0,至少3个元素才可以构成等差序列

填表顺序

固定倒数第一个数,枚举倒数第二个数,从哈希表中找符合等差的数

返回值 :dp表的总和

优化找符合等差序列的数 a

使用哈希表<元素,下标数组> ,dp之前,将其元素和下标数组(一个元素可能有多个)对应关系进行绑定

这里的数虽然不会溢出,但是其运算过程中可能会溢出,因此这里哈希表中类型改为long类型

java 复制代码
class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;
        int[][] dp =  new int[n][n];
        int[] pairs = new int[n];
        //使用long类型,计算过程中可能会溢出
        Map<Long,List<Integer>> hash = new HashMap<>();
        //将值和下标数组对应
        for(int i = 0;i < n;i++){
            long tem = (long)nums[i];
            if( !hash.containsKey(tem)){
                hash.put(tem,new ArrayList<>());
            }
            hash.get(tem).add(i);
        }
        int sum = 0;
        for(int j = 2;j < n;j++){//倒数第一个数
            for(int i = 1;i < j;i++){//倒数第二个数
                long x = 2L * nums[i] - nums[j];
                if(hash.containsKey(x)){
                    //所有符合要求的下标
                   for(int k : hash.get(x)){
                    if(k < i){
                        dp[i][j] += dp[k][i] + 1;
                    }else{
                        //下标是递增存储,不符合要求后面也都不符合
                        break;
                    }
                   }
                   sum += dp[i][j];
                }
            }
        }
        return sum;
    }
}
相关推荐
计算机学姐1 小时前
基于SpringBoot的高校爱心慈善管理系统
java·vue.js·spring boot·后端·spring·tomcat·mybatis
黎阳之光1 小时前
AI黑光相机|赋能低空全域感知,筑牢低空经济全天候视觉防线
人工智能·物联网·算法·安全·数字孪生
大梦想家a1 小时前
基于Servlet+JSP的小区物业管理系统设计与实现(Bootstrap+MySQL)
java·servlet·bootstrap
手写码匠1 小时前
华为云Flexus+DeepSeek征文|DeepSeek R1 推理优化实战:让复杂任务的回答又快又稳
人工智能·深度学习·算法·aigc
Cabbage_acmer1 小时前
cf训练-gpt
算法
wenyq71 小时前
LeetCode 438. Find All Anagrams in a String
算法·leetcode
LB21121 小时前
力扣102 198 70 55
数据结构·算法·leetcode
都叫我大帅哥1 小时前
Apache Tika 4.0 正式发布:RAG 时代文件解析的终极答案
java·spring boot·spring