输入一串数字例如: 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