阿里巴巴牛客笔试题一

原题

题目

小强现在有n个物品,每个物品有两种属性xi​和yi​.他想要从中挑出尽可能多的物品满足以下条件:对于任意两个物品xi和xj,满足xi​<xj​且yi​<yj​或者xi​>xj​且yi​>yj​.问最多能挑出多少物品。

要求

进阶:时间复杂度O(nlogn) ,空间复杂度 O(n)

输入 
第一行输入一个正整数T.表示有T组数据.
对于每组数据,第一行输入一个正整数n.表示物品个数.
接下来两行,每行有n个整数.
第一行表示n个节点的x属性.
第二行表示n个节点的y属性.
输出
输出T行,每一行对应每组数据的输出.
 测试用例

输入例子:

2
3
1 3 2
0 2 3
4
1 5 4 2 
10 32 19 21

输出例子:

2
3

我写的代码

这个代码确实不太能看,因为完全就是让我干啥我干啥,就普普通通用代码实现一下问题,压根儿没有考虑时间、空间复杂度等各种情况,用例可以过,但是提交严重超时超内存。因为我本来就是想的,先写出来,再优化,但是优化的时候,我不知道从何下手,让chatgpt优化,它用的是排序加二分,但是结果不对,我也懒的去给它纠错,就去查了一下大佬们的做法。

import java.util.Scanner;

public class Main {
    public static void main(String[] args){
         Scanner in = new Scanner(System.in);
         int N = in.nextInt();
         int[] r=new int[N];//用来存放每组数据的结果
         for(int i=0;i<N;i++){//初始化结果数组
             r[i]=0;
         }
         for(int i=0;i<N;i++){
             int n=in.nextInt();
             int[][] arr=new int[2][n];
             for(int j=0;j<n;j++) {//第一行是x的值
                 arr[0][j]=in.nextInt();
             }
             for(int j=0;j<n;j++){//第二行是y的值
                 arr[1][j] = in.nextInt();
             }
             for(int j=0;j<n;j++){
                 for(int k=j+1;k<n;k++){
                     if(arr[0][j]<arr[0][k] && arr[1][j]<arr[1][k]){
                         r[i]++;
                     }
                 }
             }
         }
         for(int i=0;i<N;i++){
             System.out.println(r[i]);
         }

    }
}

最长上升子序列DP和结合二分法优化DP

由于我学识浅薄(数据结构和算法几乎忘完了),在看到大佬的解释之后才明白这道题是最长上升子序列问题的变形。

DP算法(动态规划O(n^2))

value[ i ] 指当以数组的arr[ i ] 结尾时,子序列的长度。

从i=0开始,value[ i ]最初都为1,遍历i之前的数组元素,取目前子序列长度value[ i ]    和    前面元素中的最长子序列长度加一之间的最大值,value[i]=max(value[ i ],value[ j ]+1),+1就是加上arr[ i ]。

跟递归的思想很像,就是看是目前已经得到的子序列长度大(初始为1),还是前面某个元素的子序列的长度+1大。如果目前已经得到的子序列长度大,就不变,继续找前面元素的下一个元素的子序列长度,比较。如果是前面某个元素的子序列长度加一更大,那目前的子序列长度就变成前面那个元素的子序列长度加一,前提是前面那个元素arr[ j ]比目前这个元素arr[ i ]小。。

   /**
     * 动态规划解决,复杂度n2
     * @param arr
     */
    private static void dp(int[] arr) {
        int maxLength = 0;
 
        // 数组元素value[i]表示以arr[i]为子序列最后一位时,递增子序列的最大长度
        int[] value = new int[arr.length];
 
        // 每次循环获取 以arr[i]为子序列最后一位时,递增子序列最大长度
        for (int i = 0; i < arr.length; i++) {
            // 默认都是长度1
            value[i] = 1;
 
            // 循环前i - 1 位value[],找出最大递增序列长度maxvalue,则value[i] = maxvalue + 1;
            for (int j = 0; j < i; j++) {
                if (arr[i] > arr[j]) {
                    value[i] = Math.max(value[j] + 1, value[i]);
                }
            }
 
            // 找出最大的那个
            maxLength = Math.max(maxLength, value[i]);
        }
 
        System.out.println(maxLength);
    }
结合二分法优化之后的DP算法

DP算法虽牛,但仍然是O(n^2)复杂度上的算法,利用时间复杂度是O(log2n)的二分法进行优化后,就会出现一个非常非常牛的算法。

value[maxLength]指长度为maxLength的上升子序列的最后一位(是arr[n]里的)。

先maxLength初始化为1,让value[maxLength]=arr[0],再遍历数组arr[n]。

如果arr[ i ] >value[maxLength],就把这个数加进来,所以子序列长度加一maxLength++,子序列最后变为这个数value[maxLength]=arr[ i ]。

如果arr[ i ]<value[maxLength],就用二分法在前面找,一直找到这个arr[ i ]大于的值,用arr[ i ]替换这个值的后一个值,也就是替换掉第一个大于arr[ i ]的值。因为arr[ i ]更小,后面数字比它大的概率更大,得到更长的子序列的可能性更大。

举例:1 5 7 3 2 6
arrvaluemaxlengthvalue[maxlength]tlrmidi ? value[mid]
1 5 7 3 2 6111////
1 5 7 3 2 61 525////
1 5 7 3 2 61 5 737////
1 5 7 3 2 61 5 737/1323<5
/1113>1
1 3 737221//
1 5 7 3 2 61 3 737/1322<3
/1112>1
1 2 737221//
1 5 7 3 2 61 2 737/1326>2
33336<7
1 2 636332//
   private static void binarySearch(int[] arr) {
        // 长度加1是为了好理解,value[maxLength]即表示maxLength长度的子序列最后一位的值
        int[] value = new int[arr.length + 1];
 
        // 初始化第一个数
        int maxLength = 1;
        value[1] = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > value[maxLength]) {
                // 大于目前最大长度的子序列的最后一位,给value[]后边续上
                maxLength++;
                value[maxLength] = arr[i];
            } else {
                // 小于目前最大长度的子序列的最后一位,查找前边部分第一个大于自身的位置
                // 更新它
                int t = find(value, maxLength, arr[i]);
                value[t] = arr[i];
            }
        }
 
        System.out.println(maxLength);
    }
 
    // 二分查找
    private static int find(int[] value, int maxindex, int i) {
        int l = 1, r = maxindex;
 
        while (l <= r) {
            int mid = (l + r) / 2;
 
            if (i > value[mid]) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
 
        return l;
    }

大佬的代码

大佬的思路:先将Good按照x从小到大排列,再找到y的最长上升子序列长度即可。

大佬讲解的链接:最长子序列DP和二分法_使用二分法寻找最长子列-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/xyjy11/article/details/118032689

我的理解:题目说,满足xi​<xj​且yi​<yj​或者xi​>xj​且yi​>yj​,也就是说,x与y的递变趋势相同,按照x从小到大排列,那么就找到y的递增序列,全都挑出来,就满足任意两个xi​<xj​且yi​<yj​或者xi​>xj​且yi​>yj​。那么这个序列的长度就是我们要找的物品的个数。

直接创建一个类来封装Good的x和y属性,避开了二维数组的不便,重定义了compareTo函数便于比较。

需要注意的是,在排序过程中,对于相同大小的x,需要将大的y排在前边,因为排在后边,因为x不递增,会导致错误的结果。例如(1,2) (1,3) (1,4),这一组中最长子序列长度正确应为1,但将小y排在前边,所有的 y序列 会算出3的错误结果。

   public static class Good implements Comparable{
        public int x;
        public int y;
 
        public Good(int xx, int yy) {
            x = xx;
            y = yy;
        }
 
        @Override
        public int compareTo(Object o) {
            Good good = (Good) o;
            if (this.x > good.x) {
                return 1;
            } else if (this.x < good.x) {
                return -1;
            } else {
                //x相同,y大的放前边
                if (this.y > good.y) {
                    return -1;
                } else if (this.y < good.y){
                    return 1;
                }
 
                return 0;
            }
        }
    }
 
   public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
 
        int num = sc.nextInt();
 
        for (int i = 0; i < num; i++) {
            int goodsNum = sc.nextInt();
 
            int[] linearr1 = new int[goodsNum];
            int[] linearr2 = new int[goodsNum];
            for (int j = 0; j < linearr1.length; j++) {
                linearr1[j] = sc.nextInt();
            }
            for (int j = 0; j < linearr2.length; j++) {
                linearr2[j] = sc.nextInt();
            }
            sc.nextLine();
 
            Good[] goods = new Good[goodsNum];
            for (int j = 0; j < goods.length; j++) {
                goods[j] = new Good(linearr1[j], linearr2[j]);
            }
 
            // 按x升序排序
            Arrays.sort(goods);
 
            // 剩下的就是按y的排列来获取最大上升子序列长度问题,代码与上边两个简单的几乎一致
            // 动态规划dp O(n^2)
            dp(goods);
 
            // 二分查找优化 O(nlog2n)
            binarySearch(goods);
        }
    }
 
    private static void binarySearch(Good[] arr) {
        int[] value = new int[arr.length +1];
 
        int maxLength = 1;
        value[1] = arr[0].y;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i].y > value[maxLength]) {
                value[++maxLength] = arr[i].y;
            } else {
                int t = find(value, maxLength, arr[i].y);
                value[t] = arr[i].y;
            }
        }
 
        System.out.println(maxLength);
    }
 
    /**
     * 动态规划解决,复杂度n2
     * @param arr
     */
    private static void dp(Good[] arr) {
        int maxLength = 0;
 
        // 数组元素value[i]表示以arr[i]为子序列最后一位时,递增子序列的最大长度
        int[] value = new int[arr.length];
 
        // 每次循环获取 以arr[i]为子序列最后一位时,递增子序列最大长度
        for (int i = 0; i < arr.length; i++) {
            // 默认都是长度1
            value[i] = 1;
 
            // 循环第0位到第 i - 1 位value[],找出最大递增序列长度maxvalue,则value[i] = maxvalue + 1;
            for (int j = 0; j < i; j++) {
                if (arr[i].y > arr[j].y) {
                    value[i] = Math.max(value[j] + 1, value[i]);
                }
            }
 
            // 找出最大的那个
            maxLength = Math.max(maxLength, value[i]);
        }
 
        System.out.println(maxLength);
    }
 
    private static int find(int[] value, int maxindex, int i) {
        int l = 1, r = maxindex;
 
        while (l <= r) {
            int mid = (l + r) / 2;
 
            if (i > value[mid]) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
 
        return l;
    }
 

2024.3.20,又搞了一天,按照上述思路,自己写的并通过的代码

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static class Good implements Comparable{
        int x;
        int y;
        public Good(int x1,int y1) {
            x = x1;
            y = y1;
        }
        @Override
        public int compareTo(Object o) {
            Good good=(Good) o;    //强转为Good
            if (this.x>good.x){
                return 1;
            } else if (this.x< good.x) {
                return -1;
            }else {                 //如果x1=x2,那么y大的放前面
                if(this.y > good.y){
                    return -1;
                }else if(this.y < good.y){
                    return 1;
                }
            }
            return 0;
        }
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);

        int N =in.nextInt();
        for (int i = 0; i < N; i++) {
            int n=in.nextInt();
            int[][] arr=new int[2][n];

            for (int j = 0; j < n; j++) {   //记录x
                arr[0][j]=in.nextInt();
            }
            for (int j = 0; j < n; j++) {   //记录y
                arr[1][j]=in.nextInt();
            }
            Good[] goods = new Good[n];  //用结构体存储处理
            for (int j = 0; j < n; j++) {
                goods[j]=new Good(arr[0][j],arr[1][j]);
            }
            Arrays.sort(goods);    //从小到大排序
            //dp(goods);              //动态规划解决问题,但是超时
            binarySearch(goods);          //二分查找解决问题,优化动态规划
        }
    }

    public static void dp(Good[] goods){
        int maxLength=1;    //最长上升子序列长度就是所求的物品个数

        int[] value=new int[goods.length];  //value[i]指goods[i]结尾时最长上升子序列长度
        value[0]=1;
        for (int i = 1; i < goods.length; i++) {
            value[i]=1;
            for (int j = 0; j < i; j++) {
                if(goods[i].y>goods[j].y){    //如果满足对比条件,那么最长上升子序列长度加一
                    value[i]=Math.max(value[i],value[j]+1);    //最长上升子序列长度取最大值
                }
            }
            maxLength=Math.max(value[i],maxLength);
        }
        System.out.println(maxLength);
    }

    public static void binarySearch(Good[] goods){
        int maxLength=1;    //最长上升子序列长度就是所求的物品个数
        int[] value=new int[goods.length+1];  //value[i]指最长上升子序列长度为i时,最长上升子序列的最后一位
        value[maxLength]=goods[0].y;
        for (int i = 0; i < goods.length; i++) {
            if(goods[i].y>value[maxLength]){    //如果good[i].y大于最后一位,就加到子序列里
                maxLength++;
                value[maxLength]=goods[i].y;
            }else{      //如果比最后一位小,就往前用二分法找到第一个大于goods[i].y的,用goods[i].y取代它
                int t=find(value,maxLength,goods[i].y);
                value[t]=goods[i].y;
            }
        }
        System.out.println(maxLength);
    }
    public static int find(int[] value,int maxLength,int x){
        int l=1;
        int r=maxLength;
         while(l<=r) {
            int mid = (l + r) / 2;
            if (x>value[mid]){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
         return l;
    }
}

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值