牛客NC91 最长递增子序列(动态规划、贪心+二分查找)

原题链接

动态规划(时间复杂度 O(n^2))

    /**
     * return the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */
    /**
     *  动态规划
     *      状态 dp[i][2], dp[i][0]是以 arr[i] 为末尾的递增子序列的前一个数的索引
     *                     dp[i][1]是以 arr[i] 为末尾的递增子序列的长度
     *      状态转移方程:
     *          从 arr[i] 前的数中,找到比 arr[i] 小且最小的arr[j],且以 arr[j] 为末尾的递增子序列最长,
     *          dp[i][0] = j, dp[i][1] = dp[j][1] + 1
     * @param arr
     * @return
     */
    public int[] LIS (int[] arr) {
        // write code here
        if(arr.length == 0) return new int[0];
        int[][] dp = new int[arr.length][2];
        int maxInd = 0;
        dp[0][0] = -1;
        dp[0][1] = 1;
        for (int i = 1; i < arr.length; i++){
            int preInd = -1;
            for (int j = 0; j < i; j++){
                if(arr[j] < arr[i]){
                    if(preInd == -1)
                        preInd = j;
                    else {
                        // 若长度相同,取末尾数的字典序最小的
                        if(dp[j][1] > dp[preInd][1] || (dp[j][1] == dp[preInd][1] && arr[j] < arr[preInd]))
                            preInd = j;
                    }
                }
            }
            dp[i][0] = preInd;
            if(preInd == -1)
                dp[i][1] = 1;
            else
                dp[i][1] = dp[preInd][1] + 1;

            // 长度相同,得到字典序小的
            if(dp[i][1] > dp[maxInd][1] || (dp[i][1] == dp[maxInd][1] && arr[i] < arr[maxInd])){
                maxInd = i;
            }
        }

        int[] res = new int[dp[maxInd][1]];
        int i = dp[maxInd][1] - 1;
        while (maxInd != -1){
            res[i--] = arr[maxInd];
            maxInd = dp[maxInd][0];
        }

        return res;
    }

贪心 + 二分查找 (时间复杂度 O(nlogn))

    /**
     * 贪心 + 二分查找
     *      维护一个数组 d[], d[i]是长度为i + 1的递增子序列的末尾元素的值
     *      最开始时dp[0] = arr[0]
     *      maxLen[i]表示以arr[i]为结尾的递增子序列的最大长度
     *
     * 核心思想:
     *      保证d[]中的元素都是符合条件的元素中的最小的那一个,即让d[]的递增趋势最慢,
     *      d[]的递增序列长度才能最大。
     *
     * @param arr
     * @return
     */
    public int[] LIS2 (int[] arr) {
        if(arr.length == 0) return new int[0];
        int[] d = new int[arr.length];
        d[0] = arr[0];
        int[] maxLen = new int[arr.length];
        maxLen[0] = 1;
        int len = 1;

        for (int i = 1; i < arr.length; i++) {
            if(arr[i] > d[len - 1]) {
                d[len] = arr[i];
                len++;
                maxLen[i] = len;
            }
            else {
                // 找到d中第一个比arr[i]大的元素,替换成arr[i]
                int l = 0, r = len - 1;
                int mid = (l + r) / 2;
                /*
                    二分查找,区间左闭右闭,d[mid]即为d中第一个
                    比arr[i]大的元素。
                 */
                while (l < r){
                    if(d[mid] > arr[i]){
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                    mid = (l + r) / 2;
                }
                d[mid] = arr[i];
                maxLen[i] = mid + 1;
            }
        }

        int[] res = new int[len];
        int curLen = len;
        /*
            从maxLen数组的末尾开始向前遍历,分别将
            maxLen = curLen (len, len - 1, len - 2 ... 1)的元素
            添加到res的指定位置中。

            之所以从后向前遍历,是因为如果有多个maxLen相同的元素,则要得到
            字典序较小的那个。
            反证:
            我们可以看到,当 maxLen[i] == max[i - n] && arr[i] > arr[i - n] 时,
            因为arr[i] > arr[i - n]构成递增,则maxLen[i] > max[i - n]是必然的,
            与我们的条件相违背。所以,从后往前遍历,我们始终能够得到maxLen相等的元素中
            最小的那一个。
         */
        for (int i = arr.length - 1; i >= 0; i--){
            if(maxLen[i] == curLen){
                res[--curLen] = arr[i];
            }
        }
        return res;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值