最长子序列DP和二分法

58 篇文章 0 订阅
12 篇文章 0 订阅

输入一串数字例如: 5 6 8 1 3 4 9 

输出最长递增子序列长度,示例中即 1 3 4 9或5 6 8 9 ,最大长度为4


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

        // 几个数字
        int num = sc.nextInt();

        // 读取数字
        int[] arr = new int[num];
        for (int i = 0; i < num; i++) {
            arr[i] = sc.nextInt();
        }

        // 动态规划dp O(n^2)
        dp(arr);

        //二分实现 O(nlog2n)
        //binarySearch(arr);
    }

DP实现 复杂度O(n^2)

思路就是双循环,设value[i]为元素arr[i]结尾的子序列的最大长度 , 循环元素i前边的值arr[0- j-1],value[i] = Max(value[i], value[j] + 1);  0<=j<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);
    }

二分查找优化 O(nlog2n)

在动态规划中,内层循环中循环0到i - 1来获取最大的递增序列长度+1,可否换成二分查找log2n来优化呢?但是arr数组是无序的,无法二分查找,需要换种思路

设当前已经求出的最长上升子序列长度为maxLength,声明一个数组value,value[maxLength]表示maxLength长度的子序列最后一位的值
 大致流程:第一个数arr[0]本身就是个子序列,把arr[0]放入value[1],此时最大长度maxLength=1;
接下来的数arr[i]需判断
      1、如果比当前value[maxLength]大,那么表示把arr[i]续到value[maxLength]后可以得到一个更长的子序列,即maxLength++; value[maxLength] = arr[i];
      2、如果比当前value[maxLength]小,那么得从前maxLength个数中找到一个位置j,满足 value[j-1] < arr[i] <= value[j]; 即第一个大于等于arr[i]的数,替换它(value[j] = arr[i]),因为此时以arr[i]为结尾的子序列比以原value[j]结尾的子序列更有“潜力”,因为更小所以有更多的可能得到成更长的子序列
从前maxLength个数中找到一个位置j的过程使用二分查找

   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;
    }

阿里巴巴牛客编程题

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

第一行输入一个正整数 T.表示有T组数据.
对于每组数据,第一行输入一个正整数N.表示物品个数N.
接下来两行,每行有个N整数.
第一行表示N个节点的X属性.
第二行表示N个节点的Y属性

思路:最长上升子序列的变种,需要保证xy的同升同降,即先将所有物品按x升序排列,随后在无序的y中取一个最长上升子序列即可,同样两种解法,dp和二分,但是本题在牛客上只能二分,dp会报超时。

 

需要注意的是,在排序过程中,对于相同大小的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;
    }

 

参考:https://blog.csdn.net/daoshen1314/article/details/103174215

 

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值