最长递增子序列

题目

求一个数组中最长递增子序列,要求时间复杂度尽量低。

  • 测试输入
    7
    2 1 4 3 1 5 6
  • 测试输出
    4

分析1

采取从后向前的分析思路。如果已知第 i 个元素存在于最长递增子序列中,那么前i个元素的最长递增子序列的问题可以转化为,在前 i1 个元素中所构成的最长递增子序列,加上这个第 i 个元素。由此可见子问题具有独立性,可以使用动态规划来完成。数组array[i] i=0n1 ,定义 lis[i] 表示以第 i 个元素结尾的最长递增子序列长度,则原问题为max(lis[i]) i=0n1 。要得到以 array[i] 结尾的最长递增子序列长度 lis[i] ,就要在数组中找到 array[k] k=0i1 ,要求 array[k]<array[i] lis[k] 最大。有一种情况是 array[k] 都比 array[i] 大,那么以 array[i] 结尾的序列长度就是其自身长度1。递归式为

lis[i]=1,1,max(lis[k])+1,i=0array[k]array[i] for all k=0i1if array[k]<array[i] for k=0i1 

代码1

import java.util.Scanner;

public class LIS {
    static int solution(int[] array, int[] lis) {
        int n = array.length;
        lis[0] = 1;
        for (int i = 1; i < n; i++) {
            int maxLen = 0;// array[i]之前的最长子序列长度
            for (int k = 0; k < i; k++) {
                if (array[k] < array[i] && lis[k] > maxLen) {
                    maxLen = lis[k];
                }
            }
            // 若没有找到比array[i]小的值,array[i]构成长度为1的子序列
            // 若找到则在最长递增序列末尾加上array[i]构成新最长子序列
            lis[i] = maxLen + 1;
        }
        return max(lis);
    }

    static int max(int[] array) {
        int maxValue = Integer.MIN_VALUE;
        for (int i : array) {
            if (i > maxValue) maxValue = i;
        }
        return maxValue;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = sc.nextInt();
        }
        int[] lis = new int[n];
        System.out.println(solution(array, lis));
    }
}

分析2

上述做法时间复杂度为 O(n2) ,原因在于max查找需要遍历一遍数组。如果能将查找过程优化就能得到更优的解法。应该注意一个事实,在找前 i 个元素的最长递增子序列时,应该尽量保证已经找到的子序列的末尾元素尽量小,这样第i+1个元素才更容易构成新的子序列。由此可以换一种定义方式, tail[len] 表示长度为 len 的最长递增子序列的末尾元素,如果一个新来的元素 x 大于tail[len],则这个新元素 x 可以成为新的最长递增子序列的末尾元素,len增加1,否则就要用它替换 tail[k] k=0len1 满足 tail[k]x tail[k1]<x ,这意味着长度为 k 的最长递增子序列的末尾元素更新为x len 保持不变。用更小的 x 替换了tail[k],在不影响原有结果的情况下,更容易使新来的元素成为新子序列的末尾。另外,原问题中最长递增子序列长度就是 len 。对于数组[2 1 4 3 1 5 6]每一步分析如下:

i array[i] tail
022(初始值)
111(替换2)
241 4
331 3(替换4)
411 3
551 3 5
661 3 5 6

可以看到数组 tail 就保存着最长递增子序列的所有值,且其中的每个元素总是在可选元素的最后一个。因为数组 tail 一定递增,如果将查找过程改为使用二分查找,则整个算法的时间复杂度为 O(nlog(n))

代码2

import java.util.Scanner;

public class LIS {
    static int solution2(int[] array, int[] tail) {
        int n = array.length;
        tail[0] = array[0];
        int maxLen = 1;
        for (int i = 1; i < n; i++) {
            if (array[i] > tail[maxLen - 1]) {
                tail[maxLen] = array[i];
                ++maxLen;
            } else {// 线性查找
                for (int k = 0; k < maxLen; k++) {
                    if (tail[k] >= array[i]) {
                        tail[k] = array[i];
                        break;
                    }
                }
            }
        }
        return maxLen;
    }

    static int solution3(int[] array, int[] tail) {
        int n = array.length;
        tail[0] = array[0];
        int maxLen = 1;
        for (int i = 1; i < n; i++) {
            if (array[i] > tail[maxLen - 1]) {
                tail[maxLen] = array[i];
                ++maxLen;
            } else {// 二分查找
                int pos = binarySearch(tail, maxLen, array[i]);
                tail[pos] = array[i];
            }
        }
        return maxLen;
    }

    static int binarySearch(int[] array, int limit, int value) {
        int start = 0;
        int end = limit - 1;
        while (start <= end) {
            int mid = start + (end - start) / 2;
            if (array[mid] == value) {
                return mid;
            } else if (array[mid] < value) {
                start = mid + 1;
            } else {
                end = mid - 1;
            }
        }
        return start;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = sc.nextInt();
        }
        int[] tail = new int[n];
        System.out.println(solution2(array, tail));       
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值