原题
题目
小强现在有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 ]更小,后面数字比它大的概率更大,得到更长的子序列的可能性更大。
arr | value | maxlength | value[maxlength] | t | l | r | mid | i ? value[mid] |
1 5 7 3 2 6 | 1 | 1 | 1 | / | / | / | / | |
1 5 7 3 2 6 | 1 5 | 2 | 5 | / | / | / | / | |
1 5 7 3 2 6 | 1 5 7 | 3 | 7 | / | / | / | / | |
1 5 7 3 2 6 | 1 5 7 | 3 | 7 | / | 1 | 3 | 2 | 3<5 |
/ | 1 | 1 | 1 | 3>1 | ||||
1 3 7 | 3 | 7 | 2 | 2 | 1 | / | / | |
1 5 7 3 2 6 | 1 3 7 | 3 | 7 | / | 1 | 3 | 2 | 2<3 |
/ | 1 | 1 | 1 | 2>1 | ||||
1 2 7 | 3 | 7 | 2 | 2 | 1 | / | / | |
1 5 7 3 2 6 | 1 2 7 | 3 | 7 | / | 1 | 3 | 2 | 6>2 |
3 | 3 | 3 | 3 | 6<7 | ||||
1 2 6 | 3 | 6 | 3 | 3 | 2 | / | / |
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博客https://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;
}
}