牛的学术圈 I
www.acwing.com/problem/content/3748/
-
计算次数为 10 5 10^5 105 级别,需要使用 O ( n l o g n ) O(nlogn) O(nlogn) 的算法
-
先将给定的 N N N 个数进行从大到小的排序,判断是否有 h h h 个数满足:
-
所有数大于等于 h − 1 h-1 h−1
- 因为已经进行排序了,所以只需判断 c h ≥ h − 1 c_h \geq h-1 ch≥h−1 是否成立即可
-
且最多有 L L L 个数等于 h − 1 h-1 h−1
- 暴力算法:直接遍历-> O ( n 2 ) O(n^2) O(n2)
- 二分 h h h-> O ( n l o g n ) O(nlogn) O(nlogn)
- 双指针
-
二分写法 O ( n l o g n ) O(nlogn) O(nlogn)
import java.util.*;
public class Main {
private static final int N = 100010;
private static Integer[] c = new Integer[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int L = sc.nextInt();
for (int i = 1; i <= n; i++) {
c[i] = sc.nextInt();
}
// 从大到小排序
Arrays.sort(c, 1, n + 1, (a, b) -> {
// 返回值大于0则交换
return b - a;
});
// 二分法
int left = 0, right = n;
while (left < right) {
int mid = left + right + 1 >> 1;
if (check(L, mid)) {
left = mid;
} else {
right = mid - 1;
}
}
System.out.println(left);
}
private static boolean check(int L, int x) {
for (int i = x; i > 0 && L >= 0; i--) {
if (c[i] >= x) {
return true;
} else if (c[i] == x - 1) {
L--;
} else {
return false;
}
}
return L >= 0;
}
}
双指针写法 O ( n ) O(n) O(n)
-
注意:图中的自变量为 i i i, j j j 随着 i i i 的增大而减小,应该是从右下走到左上
-
c[j]
是大于等于 i i i 的最后一个数, j j j 为其对应下标 -
c[j]
及其左侧所有数都大于等于 i i i,现要求出c[j]
右侧等于 i − 1 i - 1 i−1 的数的个数-
j
j
j 在
i
i
i 左侧,说明
j
j
j 与
i
i
i 之间存在小于
i
i
i 的数,由于控制了
c[i] >= i - 1
,所以 j j j 与 i i i 之间的数全部等于 i − 1 i - 1 i−1,据此统计个数来更新答案 - j j j 在 i i i 右侧,说明 i i i 左侧的数也全都大于等于 i i i,此时更新答案
-
j
j
j 在
i
i
i 左侧,说明
j
j
j 与
i
i
i 之间存在小于
i
i
i 的数,由于控制了
-
且当某次遍历不更新答案时,则意味着之后也必然不会更新答案了(因为没有足够大的数来满足 h h h)
import java.util.*;
public class Main {
private static final int N = 100010;
private static Integer[] c = new Integer[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int L = sc.nextInt();
for (int i = 1; i <= n; i++) {
c[i] = sc.nextInt();
}
// 从大到小排序
Arrays.sort(c, 1, n + 1, (a, b) -> {
// 返回值大于0则交换
return b - a;
});
// 双指针
int res = 0;
// notes: j是随着i的增大而减小的,和之前同增的情况不一样,j要从n开始
for (int i = 1, j = n; i <= n; i++) {
while (j > 0 && c[j] < i) {
j--;
}
if (c[i] >= i - 1 && i - j <= L) {
res = i;
} else {
break;
}
}
System.out.println(res);
}
}