牛客——最长递增子序列

https://www.nowcoder.com/practice/9cf027bf54714ad889d4f30ff0ae5481?tpId=117&&tqId=35013&rp=1&ru=/ta/job-code-high&qru=/ta/job-code-high/question-ranking

题目描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)

示例1
输入

[2,1,5,3,6,4,8,9,7]
返回值

[1,3,4,8,9]

示例2
输入

[1,2,8,6,4]
返回值

[1,2,4]
说明
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)


思路参考:
非原创,百度一波总结下来的:

一共需要两个辅助数组和一个辅助变量:
dp数组:用来存储位置i对应的最长子序列的长度
end数组:用来存储长度为i的子序列的最后一个元素的最小值
len:用来记录当前找到的最长子序列的长度

举个例子
[3,2,5,8,6,7]
end数组:
i=0: 3 长度为1的子序列为:3
end=[3]
i=1:2<3 长度为1的子序列为:2(用2替换3,因为这是是最容易使子序列扩展为长度为2的子序列的值)
end=[2]
i=2:5>2 所以长度为1的子序列可以扩展成一个长度为2的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
end=[2 5]
i=3:8>5 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 8
end=[2 5 8]
i=4:6>5&&6<8 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6(用6替换8,因为只是最容易使子序列扩展为长度为4的子序列的值)
end=[2 5 6]
i=5:7>6 所以长度为3的子序列可以扩展成一个长度为4的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6
长度为4的子序列为:2 5 6 7
end=[2 5 6 7]
笔记:我们用end[i]来存储长度为i+1的子序列的最后一个元素的最小值,因为这是最有希望使子序列扩展的值
当a[x]>end[i]时,那么长度为i+1的子序列必定可以扩展成一个i+2的子序列,如果(old=end[i+1])>a[x],那么就令
end[i+1]=a[x],他的含义是只要a[y]>a[x]即使a[y]<old,那么长度为i+2的子序列也必然可以扩展为一个长度为i+3的子序列。
所有最终结束时end数组为[2,5,6,7]即end[i]表示长度为(i+1)的子序列的最后一个元素值。

遍历结束后也会生成 dp数组:
arr:[3,2,5,8,6,7]
dp :[1 1 2 3 3 4]
len=4
dp[i]表示到arr[i]为止最长的递增子序列,得到字典序最小的元素只需要倒过来遍历dp,依次输出最先遇到的长度为len,len-1,len-2…1的arr元素即可。
res用来存储字典序最小的最长子序列。
即: len=4 dp[5]==4 ==>res[3]=arr[5]=7
len=3 dp[4]==3 ==>res[3]=arr[4]=6
dp[3]==3 pass因为需要的是字典序最小的
len=2 dp[2]==2 ==>res[3]=arr[2]=5
len=1 dp[1]==1 ==>res[3]=arr[1]=2
dp[0]==1 pass因为需要的是字典序最小的
结果:res=[2,5,6,7]

笔记:设dp[x]==dp[y]&&x<y那么a[x]必定大于a[y],因为如果a[x]<a[y],那么dp[x]对应的子序列必定可以扩展为长度dp[x]+1的子序列即dp[y]=dp[x]+1>dp[x],矛盾,所以a[x]>a[y]


import java.util.*;


public class Solution {
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */


    public int[] LIS (int[] arr) {

        // write code here
        // DP[] 代表前i格字符内最长递增序列的递增位数

        if(arr.length <=1){
            return arr;
        }

        //一共需要两个辅助数组和一个辅助变量:
        //dp数组:用来存储位置i对应的最长子序列的长度
        //end数组:用来存储长度为i的子序列的最后一个元素的最小值
        //len:用来记录当前找到的最长子序列的长度

        int[] DP = new int[arr.length];
        int[] end = new int[arr.length]; // end数组大小不确定,直接取最大

        DP[0] = 1;
        end[0] = arr[0];
        int len = 1;

        for(int i = 1; i < arr.length; i++){
            if(arr[i] > end[len - 1]){ // 如果某个元素比当前最长子序列的最大元素还大,直接加到序列末尾
                end[len++] = arr[i];
                DP[i] = len;
                continue;
            }
            // 如果不是,则找到该元素所在的位置,必须用二分,否则会超时!
            int index = findFirstIndex(end, len, arr[i]);
            end[index] = arr[i];
            DP[i] = (index+1);
        }

        int[] res = new int[len];

        for(int k = arr.length - 1; k >=0; k--){
            if(len == 0){
                break;
            }
            if(DP[k]==len){
                res[len-1] = arr[k];
                len--;
            }
        }

        return res;

    }

    private int findFirstIndex(int[] end, int len, int key){
        int left = 0;
        int right = len-1;
        while(left <= right){
            int mid = (left + right)>>1;
            if(end[mid] < key){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        //return end[left]<key ? (left+1):left;
        return left;
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值