最长递增子序列相关问题

第一题

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例

示例1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

分析

第一种方法:动态规划,设dp[i]表示以数组下标i结尾的最长序列,那么可以得到下列递推公式:
d p [ j ] = d p [ i ] + 1 ( 0 ≤ i < j 且 a r r [ j ] > a r r [ i ] , d p [ i ] 是 满 足 条 件 的 最 大 值 ) dp[j] = dp[i]+1(0 \le i <j且arr[j] > arr[i],dp[i]是满足条件的最大值) dp[j]=dp[i]+1(0i<jarr[j]>arr[i],dp[i])

public int lengthOfLIS(int[] nums) {
        if(nums.length == 0) {
            return 0;
        }
		int[] dp = new int[nums.length];
		dp[0] = 1;
		int res = Integer.MIN_VALUE;
		for(int i = 1;i<nums.length;i++) {
			int mav = 0;
			for(int j = 0;j<i;j++) {
				if(nums[i]>nums[j]) {
					mav = Math.max(mav, dp[j]);
				}
			}
			dp[i] = mav+1;
			res = Math.max(res, dp[i]);
		}
		return res>dp[0]?res:dp[0];
	 }

时间复杂度为o(n2)

第二种方法:动态规划+二分查找,这里我们设dp[i]表示最长升序序列长度为i的最小尾数,这里可以证明dp是非递减的,下面给出证明:
当k >i时,若dp[i]>dp[k],因为dp[k]代表长度为k的升序序列最小尾数,那么在这个序列中第i个数小于第k个数,即dp[i] < dp[k],这与假设矛盾,谷dp[i] <= dp[k],dp非递减
状态转移方程:
设当前遍历数组中的第i位,此时dp数组的有序部分最后一位为dp[k]

  1. 如果nums[i] > dp[k],那么dp[k+1] = nums[i]
  2. 如果nums[i]<=dp[k],那么就利用二分查找找出其插入的位置,然后更新该位置上的数值。
public int lengthOfLIS(int[] nums) {
         int[] dp = new int[nums.length+1];
         //初始化dp
        dp[1] = nums[0];
        //指向dp数组中最后一个有序数
        int size = 1;
        for(int i = 1; i < nums.length; i++) {
            int position = findPosition(dp,1,size,nums[i]);
            dp[position] = nums[i];
            if(position > size) {
                size++;
            }
        }
       
        return size;
	 }
	 //二分查找
      public int findPosition(int[] arr,int begin,int end,int target) {
        int i = begin;
        int j = end;
        while(i <= j) {
            int mid = (i + j) / 2;
            if(target > arr[mid]) {
                i = mid+1;
            } else {
                j = mid-1;
            }
        }

        return i;
    }

时间复杂度为o(nlogn)

第二题

给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
示例1
输入:
[2,1,5,3,6,4,8,9,7]
返回值:
[1,3,4,8,9]

示例2
输入:
[1,2,8,6,4]
返回值:
[1,2,4]
说明:
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)

分析

假设dplen[i]是以nums[i]结尾的序列的长度,那么在同一长度下,最右边的那个元素一定是字典序最小的,举个例子:序列“215364897”的每个元素对应的长度如下

215364897
112233454

可以看出每个长度对应的序列是降序,如长度为1时,序列为“21”,长度为2时,序列为“53”,因此要找出最长序列的最小字典序,从后往前遍历找到遇到的第一个对应长度的字符,如找到长度为5的最小字典序:

  1. 从后往前遍历找到第一个对应长度为5的字符,这里是9
  2. 继续从后往前遍历找到对应长度为4的字符,这里是8,
  3. 继续从后往前遍历找到对应长度为3的字符,这里是4,
  4. 继续从后往前遍历找到对应长度为2的字符,这里是3,
  5. 继续从后往前遍历找到对应长度为1的字符,这里是1,
    长度为5对应的最小字典序列就是13489
public class Solution {
    public int[] LIS (int[] nums) {
        // write code here
        //dp[i]数组保存的是最长上升子序列长度为i的最下尾数
        int[] dp = new int[nums.length+1];
        //dplen[i]保存的是以nums[i]结尾的序列长度
        int[] dplen = new int[nums.length+1];
        dp[1] = nums[0];
        int size = 1;
        dplen[0] = 1;
        for(int i = 1; i < nums.length; i++) {
            int position = findPosition(dp,1,size,nums[i]);
            dp[position] = nums[i];
            if(position > size) {
                size++;
            }
             dplen[i] = position;
        }
        int [] res = new int[size];
        //从后往前遍历
        for(int i = nums.length-1,j = size; i >= 0; i--) {
            if(dplen[i] == j) {
                res[--j] = nums[i];
            }
        }
        return res;
    }
        
     public int findPosition(int[] arr,int begin,int end,int target) {
        int i = begin;
        int j = end;
        while(i <= j) {
            int mid = (i + j) / 2;
            if(target > arr[mid]) {
                i = mid+1;
            } else {
                j = mid-1;
            }
        }
        return i;
    }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值