高频4. 最长递增子序列 及其变形题的各种解法 Java codetop

题目描述

最长递增子序列
给你一个整数数组 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 。

示例2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

各种解法

动态规划

子序列不要求连续,子数组才要求连续

class Solution {
    public int lengthOfLIS(int[] nums) {
        // dp[i]的定义为:包含第i个元素的最长递增子序列的长度
        int[] dp = new int[nums.length];
        //初始化默认每个数字结尾的长度都有 1,自己
        dp[0] = 1;
        int maxans = 1; // 最终结果,最终是所有dp中的最大值
        for(int i=1; i<nums.length; i++){
            dp[i]=1; // 一定要赋值,因为可能第2个数进不了if,导致取值为0
            for(int j=0; j<i; j++){
                if(nums[j]<nums[i]){
                    dp[i] = Math.max(dp[i], dp[j]+1);
                }
            }
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}
解题思路
  • dp[i]表示考虑前 i 个元素,以第 i 个数字结尾的最长子序列的长度,nums[i]必须被选中
  • 状态转移方程:dp[i] = max(dp[j]) + 1, 其中0≤j<i且num[j]<num[i],以之前比自己小的元素结尾的最大的结果+1
  • 最终的结果是dp数组中的最大值

时间复杂度:O(n^2)

动态规划+二分查找

需要把时间复杂度从O(n^2)提升到O(nlogn),自然地往二分查找的方向想。
动态规划O(n^2)来源于求dp数组是o(n),dp数组的每个值是O(n)
现在可优化的点在dp数组的每个值的求取,降到O(logn)。需要重新设计,原来的dp变成现在的tails数组

public static int longestSubArr(int[] nums){
        // 因为要求O(NlogN),所以dp的思路就达不到了,需要用二分查找的思路
        int[] tails = new int[nums.length]; // tails[k]记录长度为k+1的子序列的尾部元素值
        int res = 0;
        for(int num : nums){
            int left = 0, right = res;
            // 前闭后开,找到第一个num大的数,更新他
            while(left<right){
                int mid = left + (right-left)/2;
                if(tails[mid] < num){
                    left = mid+1;
                }else{ // 等于的还是right往左走
                    right = mid;
                }
            }
            tails[left] = num;
            if(right == res) res++;
        }
        return res;
    }
解题思路
  • 状态变为:tails[k]表示长度为k+1的递增子序列他的结尾元素的最小值,列举了各个长度的上升子序列可以拥有的最小的结尾。(想到这种状态,应该是如果你的递增子序列要尽可能长,那么前面的子序列的结尾要尽可能小)
  • 转移方程:res为tails的当前长度,直到当前的最长上升子序列长度。也就是说如果现在出现的数大于tails记录的所有长度的递增子序列的最小值,那么他应该是一个更长递增子序列的结尾。res应该+1
  • 如果tails区间中存在比当前num大的结尾,从左往右第1个,更新为当前值。(因为这个位置,前面的结尾数比自己小,后面的结尾数比自己大,当前num可以作为前面结尾数的后一个数,也就是子序列长度+1,刚好应该是在tails数组后一个结尾数的位置,刚好又比原先此位置的结尾数小,所以可以更新该位置。)
  • 如果tails区间中不存在比当前num大的结尾,直接把当前num放到tails数组后面一格,因为他可以作为前面所有的结尾。res++;
  • 最终返回是res的长度,也就是最长上升子序列长度。

时间复杂度:O(nlogn)

变形题

1. 要求输出最长递增子序列路径,具体序列,字典序最小的那个

字典序最小的那个,就是对tails数组的每个位置做最后更新的那几个原始数字组成。

public class Solution {
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */
    public int[] LIS (int[] nums) {
        // write code here
        int[] tails = new int[nums.length]; // tails[k]记录长度为k+1的子序列的尾部元素值
        int res = 0;
        int[] dp = new int[nums.length]; // 再记录一下每个位置的最长序列长度
        for(int i=0; i<nums.length; i++){
            int num = nums[i];
            int left = 0, right = res;
            // 前闭后开
            while(left<right){
                int mid = left + (right-left)/2;
                if(tails[mid] < num){
                    left = mid+1;
                }else{
                    right = mid;
                }
            }
            tails[left] = num;
            dp[i] = left+1; // 记录该位置的最长序列长度
            if(right == res) res++;
        }
        // 对了后续的这些步骤和 dp数组
        // 完成求上面的最长递增子序列的长度后,开始确定该递增子序列的具体序列
        int[] subarr = new int[res];
        int len = res;
        for(int i=nums.length-1; i>=0; i--){
            if(dp[i] == len){
                subarr[len-1] = nums[i];
                len--;
            }
        }
        return subarr;
    }
}
解题思路
  • 按照原先的思路求出最大递增子序列的长度,然后在求每个数对tails数组更新情况的过程中,记录每个数对应的最大递增子序列的长度记在dp里面。
  • 按照上面的一套流程,求完之后,根据填充好的dp数组和最大的递增子序列长度,然后从后往前找各个长度下的对应的数字
  • 从后往前找的原因是,如果后面的数和前面的数所形成的最长递增子序列的长度相等, 那么肯定是后面的数小于前面的数的情况,不然后面这个数的最长递增子序列会比前面的数对应的要长。所以字典序最小,要选后面的数。

2. 阿里变形体

题目描述

小强现在有 n 个物品,每个物品有两种属性 x i x_{i} xi y i y_{i} yi.他想要从中挑出尽可能多的物品满足以下条件:对于任意两个物品 i 和 j ,满足 x i < x j x_{i}<x_{j} xi<xj y i < y j y_{i}<y_{j} yi<yj或者 x i > x j x_{i}>x_{j} xi>xj y i > y j y_{i}>y_{j} yi>yj.问最多能挑出多少物品。时间复杂度要求O(nlogn).

输入描述:

第一行输入一个正整数.表示有组数据.
对于每组数据,第一行输入一个正整数.表示物品个数.
接下来两行,每行有个整数.
第一行表示个节点的属性.
第二行表示个节点的属性.

输出描述:

输出行,每一行对应每组数据的输出.

输入例子1:

2
3
1 3 2
0 2 3
4
1 5 4 2
10 32 19 21

输出例子1:

2
3

解法
二分查找
import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        for(int p=0; p<T; p++){
            int n = sc.nextInt();
            int[][] arr = new int[n][2];
            for(int i=0; i<n; i++){
                arr[i][0] = sc.nextInt();
            }
            for(int i=0; i<n; i++){
                arr[i][1] = sc.nextInt();
            }
            // 先按照x进行升序排列,如果x相同,就按照y降序排列(x相等也是不符合,所以直接y降序强行不符合)
            Arrays.sort(arr, (o1,o2)->{
                if(o1[0] > o2[0])
                    return 1;
                else if(o1[0] < o2[0])
                    return -1;
                else
                {
                    if(o1[1] > o2[1])
                        return -1;
                    else if(o1[1] < o2[1])
                        return 1;
                    else
                        return -1;
                }
            });
            int[] nums = new int[n];
            for(int i=0; i<n; i++){
                nums[i] = arr[i][1];
            }
            System.out.println(longestSubArr(nums));
        }
    }
    
    public static int longestSubArr(int[] nums){
        // 求y的严格递增子序列的长度
        // 因为要求O(NlogN),所以dp的思路就达不到了,需要用二分查找的思路
        int[] tails = new int[nums.length]; // tails[k]记录长度为k+1的子序列的尾部元素值
        int res = 0;
        for(int num : nums){
            int left = 0, right = res;
            // 前闭后开
            while(left<right){
                int mid = left + (right-left)/2;
                if(tails[mid] < num){
                    left = mid+1;
                }else{
                    right = mid;
                }
            }
            tails[left] = num;
            if(right == res) res++;
        }
        return res;
    }
}
解题思路
  • 总体的思路,是对给的二维数组排序,在保证x升序的情况下,求出y最大递增子序列。
  • 其中的一个小细节是,x相等的情况,是不包含在结果中的,所以索性让这种情况下的y降序,从而肯定不会满足条件。
  • 排好序后,对y求最大递增子序列。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值