最长递增子序列+java

二分查找——链接 (这个一不小心就会错)

方法1,时间复杂度为O(N*N)

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums == null || nums.length == 0)
            return 0;
        int [] dp = new int[nums.length];
        dp[0] = 1;
        int maxlen = 1;
        for(int i = 1;i<dp.length;i++){
            dp[i] = 1;
            for(int j = 0;j<i;j++){
                if(nums[i]>nums[j] && dp[j]+1>dp[i]){
                    dp[i] = dp[j]+1;
                }
            }
            if(dp[i]>maxlen)
                maxlen = dp[i];
        }
        return maxlen;
    }
}

方法2,时间复杂度O(nlogn)——解析和代码(这里更详细)

算法的执行流程:

1、设置一个有序数组 tail,初始时为空;

数组命名为 tail 即 PPT 中各个行表示的数组(是一个“上升子序列”)的结尾,注意:有序数组 tail 虽然有“上升”的性质,但它不是每时每刻都表示问题中的“最长上升子序列”(下文还会强调),不能命名为 LIS,有序数组 tail 是用于求解 LIS 问题的辅助数组。
2、在遍历数组 nums 的过程中,每来一个新数 num,如果这个数严格大于有序数组 tail 的最后一个元素,就把 num 放在有序数组 tail 的后面,否则进入第 3 点;

注意:这里的大于是“严格”大于,不包括等于的情况。
3、在有序数组 tail 中查找第 1 个等于大于 num 的那个数,试图让它变小;

如果有序数组 tail 中存在等于 num 的元素,什么都不做,因为以 num 结尾的最短的“上升子序列”已经存在;
如果有序数组 tail 中存在大于 num 的元素,找到第 1 个,让它变小,这样我们就找到了一个“结尾更小”的“相同长度”的上升子序列。
这一步可以使用“二分查找法”。

4、遍历新的数 num ,先尝试上述第 2 点,第 2 点行不通则执行第 3 点,直到遍历完整个数组 nums,最终有序数组 tail 的长度,就是所求的“最长上升子序列”的长度。

算法的关键之处:

以上算法能够奏效的关键是:

最开始提到的“基本思想”,可以认为是一种“贪心选择”的思想:只要让前面的数尽量小,在算法的执行过程中,第 2 点被执行的机会就越多。

 

package com.niuke.huawei20190828;

import java.util.Scanner;

/**
 * @Author huanglei
 * @Date 2019-09-02 16:59
 * @Description TODO 最长上升子序列  {1,3,0,2}   它的最长上升子序列为{1,3} 长度为2
 **/
public class Main03_1 {
    public static void main(String [] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int [] numsA = new int[n];
        input(sc, numsA);
        System.out.println(LengthOfLIS(numsA));
    }

    //最长递增子序列的长度,    最优方法,时间复杂度O(nlogn),空间复杂度,o(n)
    public static int LengthOfLIS(int [] arr){
        int len = arr.length;
        if(len<=1)// 特判
            return len;
        // tail 数组的定义:长度为 i + 1 的上升子序列的末尾最小是几
        int [] tail = new int[len];
        tail[0] = arr[0];// 遍历第 1 个数,直接放在有序数组 tail 的开头
        int end = 0;// end 表示有序数组 tail 的最后一个已经赋值元素的索引
        for(int i = 1;i<len;i++){
            // 这里,因为当前遍历的数,有可能比有序数组 tail 数组实际有效的末尾的那个元素还大
            // 【逻辑 1】因此 end + 1 应该落在候选区间里
            if(arr[i]>tail[end]){
                end++;
                tail[end] = arr[i];
            }else {
                int left = 0;
                int right = end;
                while(left<right){
                    // 选左中位数不是偶然,而是有原因的,原因请见 LeetCode 第 35 题题解
                    int mid = (left+right)>>>1;
                    if(tail[mid]<arr[i]){
                        // 中位数肯定不是要找的数,把它写在分支的前面
                        left = mid+1;
                    }else {
                        right = mid;
                    }
                }
                // 走到这里是因为 【逻辑 1】 的反面,因此一定能找到第 1 个大于等于 nums[i] 的元素
                // 因此,无需再单独判断
                tail[left] = arr[i];
            }
        }
        // 此时 end 是有序数组 tail 最后一个元素的索引
        // 题目要求返回的是长度,因此 +1 后返回
        end++;
        return end;
    }
    public static void input(Scanner sc, int [] nums){
        for(int i = 0;i<nums.length;i++){
            nums[i] = sc.nextInt();
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值