最长递增子序列问题


题目描述

给定一个未排序的整数数组,找出最长递增子序列。
例如给定数组[10, 9, 2, 5, 3, 7, 101, 18],最长递增子序列就是[2, 3, 7, 101],长度就是4,最长递增子序列不一定只有一个,只要求出最长的长度。

解法一

动态规划法,定义一个数组dp,dp[i]代表了第i个数为结尾的最长递增子序列长度。当计算第dp[i]时,比较i位置的值和前面的所有值相比,如果值大于前面j处的值,就记录当前最大的dp[i]为dp[j]+1,dp[i]中的最大值。遍历过程中可以设置一个值记录最大递增子序列。
时间复杂度:由于每次判断dp[i],需要和i前面的所有值比较,因此,时间复杂度为O(n2)。
实现:

public static int longestSubstring(int[] arr){
    if (arr == null || arr.length <= 0) return 0;
    int[] dp = new int[arr.length];
    dp[0] = 1;
    int max = dp[0];
    for (int i = 1; i < arr.length; i++){
        dp[i] = 1;
        for (int j = 0; j < i; j++){
            if (arr[j] < arr[i]) {
                dp[i] = dp[i] >= dp[j] + 1 ? dp[i] : dp[j] + 1;
            }
        }
        max = max >= dp[i] ? max : dp[i];
    }
    return max;
}

解法二

解法二中,同样定义一个数组h[],h[i]表示的是长度为i+1的递增子序列的最小末尾。h数组是有序的,每次遍历一个数,就在数组h的有效区中(填充了数据的部分)找到第一个大于自己的数,将其覆盖。
时间复杂度:O(nlogn)
实现:

public static int longestSubstringFast(int[] arr){
    if (arr == null || arr.length <= 0) return 0;
    int[] h = new int[arr.length];
    h[0] = arr[0];
    int low = 0;
    int high = 0;
    int curr = 0;  //记录h的最后一个位置

    for (int i = 1; i < arr.length; i++){
        if (arr[i] > h[curr]) {
            h[++curr] = arr[i];  //比当前最后一个大,则直接插入在末尾
        }
        else{
            low = 0;
            high = curr;
            while (low <= high){
                if (h[high] < arr[i]) {
                    h[high+1] = arr[i];
                    break;
                }
                if (h[low] > arr[i]){
                    h[low] = arr[i];
                    break;
                }
                int mid = (low + high) / 2;
                if (arr[i] == h[mid]) break;
                else if (arr[i] > h[mid]) low = mid + 1;
                else high = mid;
            }
        }
    }

    return curr+1;
}

扩展题目

描述:给定一个N×2 的二维数组,看作是一个个二元组,例如[[a1,b1],[a2,b2],[a3,b3]]
规定:一个如果想把二元组甲放在二元组乙上,甲中的a 值必须大于乙中的a 值,甲中的b
值必须大于乙中的b 值。如果在二维数组中随意选择二元组,请问二元组最多可以往上摞
几个?
例如:[[5,4],[6,4],[6,7],[2,3]], 最大数量可以摞3 个,[2,3] => [5,4] => [6,7]
要求:实现时间复杂度O(NlogN)的解法

解法一

与上面求最长递增子序列方法一样,首先需要将数组排序,定义一个类用于表示二元组。排序规则为首先对二元组第一个数递增排序,然后第一个数相同的情况下,对第二个数递增排序。其中dp[i]表示以第i个二元组为结尾的最大长度。
时间复杂度:O(n2)
实现:
二元组定义

/**
 * 二元组类,保存两个数
 * Created by GGM on 2016/7/25.
 */
public class TwoTuples {
    private int a;
    private int b;

    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }

    public TwoTuples(int a, int b){
        this.a = a;
        this.b = b;
    }

    public boolean canBeUpper(TwoTuples twoTuples) {
        if (this.a > twoTuples.a && this.b > twoTuples.b) return true;
        return false;
    }
}

比较器定义

/**
 * Created by GGM on 2016/7/25.
 */
public class ComparatorGenerator {
    /**
     * 第一个数递增排序,第二个数递增排序
     * @return
     */
    public static Comparator<TwoTuples> orderComparator(){
        return new Comparator<TwoTuples>() {
            @Override
            public int compare(TwoTuples o1, TwoTuples o2) {
                if (o1.getA() == o2.getA()){
                    return o1.getB() > o2.getB() ? 1 : (o1.getB() == o2.getB() ? 0 : -1);
                } else {
                    return o1.getA() > o2.getA() ? 1 : -1;
                }
            }
        };
    }

    /**
     * 第一个数大递增排序,第二数递减排序
     * @return
     */
    public static Comparator<TwoTuples> reverseComparator(){
        return new Comparator<TwoTuples>() {
            @Override
            public int compare(TwoTuples o1, TwoTuples o2) {
                if (o1.getA() == o2.getA()){
                    return o1.getB() > o2.getB() ? -1 : (o1.getB() == o2.getB() ? 0 : 1);
                } else {
                    return o1.getA() > o2.getA() ? 1 : -1;
                }
            }
        };
    }
}

算法实现

public static int longestOfTwoTuples(TwoTuples[] arr){
    if (arr == null || arr.length <= 0) return 0;
    int[] dp = new int[arr.length];
    Arrays.sort(arr,ComparatorGenerator.orderComparator());
    dp[0] = 1;
    int max = dp[0];
    for (int i = 1; i < arr.length; i++){
        dp[i] = 1;
        for (int j = 0; j < i; j++){
            if (arr[i].canBeUpper(arr[j])) {  //如果当前i可以放在j上面
                dp[i] = dp[i] >= dp[j] + 1 ? dp[i] : dp[j] + 1;
            }
        }
        max = max >= dp[i] ? max : dp[i];
    }
    return max;
}

解法二

本解法同样需要对数组先排序,排序使用如下规则:对第一个数升序排序,如果第一个数相同,则第二个数降序排序。 h[i]存放的是二元组的b的值,因为相同的a的情况的下,b是按降序排序,相同的a情况下,b会覆盖第一个比它大的值,如果b的值比有效区最后一个值,那么说明是a也比有效区最后一个值,那么,将其添加在后面。
实现:
二元组和比较器在上面定义,此处就省略了
算法实现

public static int longestOfTwoTuplesFast(TwoTuples[] arr){
    if (arr == null || arr.length <= 0) return 0;
    int[] h = new int[arr.length];
    Arrays.sort(arr,ComparatorGenerator.reverseComparator());
    h[0] = arr[0].getB();
    int low = 0;
    int high = 0;
    int curr = 0;  //记录h的最后一个位置

    for (int i = 1; i < arr.length; i++){
        if (arr[i].getB() > h[curr]) {
            h[++curr] = arr[i].getB();  //比当前最后一个大,则直接插入在末尾
        }
        else{
            low = 0;
            high = curr;
            while (low <= high){
                if (h[high] < arr[i].getB()) {
                    h[high+1] = arr[i].getB();
                    break;
                }
                if (h[low] > arr[i].getB()){
                    h[low] = arr[i].getB();
                    break;
                }
                int mid = (low + high) / 2;
                if (arr[i].getB() == h[mid]) break;
                else if (arr[i].getB() > h[mid]) low = mid + 1;
                else high = mid;
            }
        }
    }

    return curr+1;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值