算法-最长递增子序列LIS

最长连续递增序列

添加链接描述
分析
1、状态只有一个:数组索引
定义dp[i]:表示以 nums[i] 这个数结尾的最长连续递增子序列的长度

2、选择
对于当前位置nums[i],如果nums[i-1]比我大,那我就接在i-1后面,否则只能自己组队

3、base case:dp[i]=1;

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int n=nums.size();
        vector<int>dp(n,1);
        //ans必须初始化为1,因为nums只有1个数的时候不会执行下面的for循环
        int ans=1; 
        for(int i=1;i<n;i++){
            if(nums[i-1]<nums[i]){
                dp[i]+=dp[i-1];
            }
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

最长非连续递增序列

添加链接描述
与上一题不同的是:本题不需要连续。

分析
1、状态只有一个:数组索引
定义dp[i]:表示以 nums[i] 这个数结尾的最长递增子序列的长度

2、选择
上题只有两个选择,

  • 自己组队
  • 接在前一个元素后面

现在有i个选择

  • 自己组队
  • 接在前面i-1个元素中的任意一个后面

3、base case :dp[i]=1 0<=i<n
c++:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        if(n==0){
            return 0;
        }
        vector<int>dp(n,1);
        int ans=1;//ans必须初始化为1,因为nums只有1个数的时候不会执行下面的for循环
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
            ans=max(ans,dp[i]);
        }
        return ans;
    }
};

java:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n=nums.length;
        int[]dp=new int[n];
        Arrays.fill(dp,1);
        int ans=1;
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            ans=Math.max(ans,dp[i]);
        }
        return ans;
    }
}

最长递增子序列的个数

添加链接描述
除了dp数组存储递增序列的长度之外,还需要将使得dp[i]最大的路径的条数cnt存下来,所以再开一个数组存每个位置的cnt。

dp[i]:以 nums[i] 这个数结尾的最长递增子序列的长度
count[i]:以 nums[i] 这个数结尾的最长递增子序列的条数
c++:

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int>cnt(n,1);
        vector<int>dp(n,1);
        for(int i=1;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    if(dp[j]+1==dp[i]){
                        cnt[i]+=cnt[j];
                    }else if(dp[j]+1>dp[i]){
                        dp[i]=dp[j]+1;
                        cnt[i]=cnt[j];
                    }
                }
            }
        }
        int max=0,sum=0;
        for(int i=0;i<n;i++){
            if(dp[i]>max){
                max=dp[i];
                sum=cnt[i];
            }else if(dp[i]==max){
                sum+=cnt[i];
            }
        }
        return sum;
    }
};

java:

class Solution {
    public int findNumberOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        int[] count = new int[n];
        Arrays.fill(dp, 1);
        Arrays.fill(count, 1); 

        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    if (dp[j] + 1 == dp[i]) { 
                        count[i] += count[j];
                    } else if (dp[j] + 1 > dp[i]) {
                        count[i] = count[j];
                        dp[i] = dp[j] + 1;
                    }
                }
            }
        }
        int ans = 0;
        int max = -1;
        for (int i = 0; i < n; i++) {
            if (dp[i] > max) {
                ans = count[i];
                max = dp[i];
            } else if (dp[i] == max) {
                ans += count[i];
            }
        }

        return ans;
    }
}

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

添加链接描述
本质还是LIS,对当前下标索引i,遍历其后面所有元素j,然后遍历其前面的所有元素k,如果arr[i]+arr[k]==arr[j],就说明可以接上了。
在这里插入图片描述
代码:

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n=arr.size();
        vector<vector<int>>dp(n,vector<int>(n));
        for(int i=0;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                dp[i][j]=2;
            }
        }
        int ans=0;
        for(int i=1;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                for(int k=0;k<i;k++){
                    if(arr[k]+arr[i]==arr[j]){
                        dp[i][j]=max(dp[i][j],dp[k][i]+1);
                    }
                }
                ans=max(ans,dp[i][j]);
            }
        }
        return ans==2?0:ans;
    }
};

进一步优化时间复杂度:
c++:

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n=arr.size();
        vector<vector<int>>dp(n,vector<int>(n));
        for(int i=0;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                dp[i][j]=2;
            }
        }
        unordered_map<int,int>map;
        for(int i=0;i<n;i++){
            map[arr[i]]=i;
        }
        int ans=0;
        for(int i=1;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                int diff=arr[j]-arr[i];
                if(map.count(diff)>0){
                    int k=map[diff];
                    dp[i][j]=max(dp[i][j],dp[k][i]+1);
                }
                ans=max(ans,dp[i][j]);
            }
        }
        return ans==2?0:ans;
    }
};

java:

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n=arr.length;
        int[][]dp= new int[n][n];
        //base case
        for (int i = 0; i < n-1; i++) {
            for(int j=i+1;j<n;j++){
                dp[i][j]=2;
            }
        }
		//map优化
        Map<Integer,Integer>map=new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i],i); 
        }
        
        int ans=0;
        for (int i=0;i<n-1;i++){
            for (int j=i+1;j<n;j++){
                int diff=arr[j]-arr[i];
                if (map.containsKey(diff)){
                //在i的前面有一个arr[k](diff)满足arr[k]=arr[j]-arr[i]
                    int idx=map.get(diff); //取出arr[k]的下标k
                    dp[i][j]=Math.max(dp[i][j],dp[idx][i]+1);
                }
                ans=Math.max(ans,dp[i][j]);
            }
        }
        return ans==2?0:ans;
    }
}

最长理想子序列

最长理想子序列
和最长非连续递增子序列 本质是一样的
很容易写出下面的基础代码,但是超时

public int longestIdealString(String s, int k) {
    int[] dp = new int[s.length()];
    int res = 0;
    Arrays.fill(dp, 1);
    for (int i = 0; i < s.length(); i++) {
        for (int j = 0; j < i; j++) {
            // 绝对值相差不大于 k
            if (Math.abs(s.charAt(i) - s.charAt(j)) <= k) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
        res = Math.max(res, dp[i]);
    }
    return res;
}

比如当 k = 3,如果当前字母为 g,那么它只能接在 d e f g h i j 为结尾的子序列后面

按照上面的代码,我们把区间 [0, i-1] 全部遍历了,显然有很多无效分支

如何优化呢?
用 Map 记录以某个字母结尾的最长子序列的长度

这里字母最多 26 个,算上绝对值,最多会遍历 52 次,相比于 10^5 简直少太多了

class Solution {
public:
    int longestIdealString(string s, int k) {
        unordered_map<char,int>map;
        int n=s.size();
        int ans=0;
        for(int i=0;i<n;i++){
            int cur=1;
            for(int j=0;j<=k;j++){
                char l=(char)(s[i]-j); // 比 cur 少 j 的字母
                if(map.count(l)>0){
                    cur=max(cur,map[l]+1);
                }
                char r=(char)(s[i]+j); // 比 cur 多 j 的字母
                if(map.count(r)>0){
                    cur=max(cur,map[r]+1);
                }
            }
            map[s[i]]=cur; // 更新以 cur 结尾的最长子序列的长度
            ans=max(ans,cur);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值