【算法】子集(LIS最长上升子序列)

文章目录

    • 题目
    • 输入描述
    • 输出描述
    • 示例
    • 分析
    • 思路
    • 最长递增子序列
    • dp解法(2/10)
    • binarySearch + 贪心(AC)

题目

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

输入描述

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

1 < = T < = 10 2 < = n < = 100000 0 < = x , y < = 1000000000 1 <= T <= 10\\ 2 <= n <= 100000\\ 0<= x, y <= 1000000000 1<=T<=102<=n<=1000000<=x,y<=1000000000

输出描述

输出 T T T行,每一行对应每组数据的输出.

示例

输入例子:
2
3
1 3 2
0 2 3
4
1 5 4 2
10 32 19 21
输出例子:
2
3

分析

这题看上去比较绕,我们先以示例入手,简单拆解一下

在这里插入图片描述

现在,我们将目光聚焦于绿框部分,如下图所示

在这里插入图片描述

我们需要选出若干个红框,使得红框组成x属性严格递增的序列时,同时保证y属性也严格递增。且选择红框数量尽可能大

例如,我们选择1,3红框,能够实现双递增序列:
1 -> 3
0 -> 2

2,3红框无法实现双递增序列:
2 -> 3
3 -> 2
(红框元素被绑定死,不能随意组合x,y中任意下标的元素)

如果我们选择1,2,3红框,无法实现递增序列,因为2,3无法形成双递增序列

思路

  1. 设计数据结构,将x,y对应下标元素组合再一起。例如构建如下数据结构
    class Node {
    	public int x;
    	public int y;
    }
    Node[] nodes = new Node[N];
    
  2. 对Node的x属性排序,保证nodes按照x非递减排序
  3. 对排序后的y属性进行筛选,选出最长递增子序列

tip: 此处需要注意,nodes无法按照x严格递增。因为可能存在若干个具有相同x值的node。因此在排序时,如果x相同,需要按照y降序排序。因为这样在对y进行筛选时,在x相同情况下,只能选择一个y最为最终序列。
例如:
1 9 9
3 8 7
最终会只能选择3, 8 | 3,7。如果x相同,但是不按照y降序,则可能会选择3,7,8.导致重复选择相同的x,而无法保证x严格递增

最长递增子序列

目前有两种解法

  1. dp
  2. binarySearch + 贪心

dp和bs算法已经有非常多成熟的文章,感兴趣的读者可以自行了解上述两篇文章,本文只给出ac代码

dp解法(2/10)

import java.util.*;

public class Main {

    static class Node {
        public int x;
        public int y;
        public Node() {}
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        int n;
        for (int i = 0; i < T; ++i) {
            n = sc.nextInt();
            Node[] x = new Node[n];
            // 存储 x - y
            for (int j = 0; j < n; ++j) {
                Node node = new Node();
                node.x = sc.nextInt();
                x[j] = node;
            }
            for (int j = 0; j < n; ++j) {
                x[j].y = sc.nextInt();
            }
            // 排序
            Arrays.sort(x, (Node a, Node b) -> {
                if (a.x != b.x) return a.x - b.x;
                else return - (a.y - b.y);
            });
            int[] y = new int[n];
            for (int j = 0; j < n; ++j) {
                y[j] = x[j].y;
            }
            // 求最长递增子序列
            int[] dp = new int[n];
            dp[0] = 1;
            int maxn = 1;
            for (int j = 1; j < n; ++j) {
                dp[j] = 1;
                for (int k = 0; k < j; ++k) {
                    if (y[j] > y[k]) dp[j] = Math.max(dp[j], dp[k] + 1);
                }
                maxn = Math.max(maxn, dp[j]);
            }
            System.out.println(maxn);
        }
    }
}

在这里插入图片描述

binarySearch + 贪心(AC)

import java.util.*;

public class Main {

    static class Node {
        public int x;
        public int y;
        public Node() {}
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        int n;
        for (int i = 0; i < T; ++i) {
            n = sc.nextInt();
            Node[] x = new Node[n];
            // 存储 x - y
            for (int j = 0; j < n; ++j) {
                Node node = new Node();
                node.x = sc.nextInt();
                x[j] = node;
            }
            for (int j = 0; j < n; ++j) {
                x[j].y = sc.nextInt();
            }
            // 排序
            Arrays.sort(x, (Node a, Node b) -> {
                if (a.x != b.x) return a.x - b.x;
                else return - (a.y - b.y);
            });
            int[] y = new int[n];
            for (int j = 0; j < n; ++j) {
                y[j] = x[j].y;
            }
            // 求最长递增子序列
            int len = 1;
            int[] d = new int[n + 1];
            d[len] = y[0];

            for (int j = 1; j < n; ++j) {
                // y[j] 大于d当前最末尾元素
                if (y[j] > d[len]) {
                    d[++len] = y[j];
                }else {
                    int lef = 1, rig = len, ans = 0;
                    while (lef <= rig) {
                        int mid = (lef + rig) >> 1;
                        if (d[mid] < y[j]) {
                            ans = mid;
                            lef = mid + 1;
                        }else {
                            rig = mid - 1;
                        }
                    }
                    d[ans + 1] = y[j];
                }
            }
            System.out.println(len);
        }
    }
}

在这里插入图片描述

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值