题目
此题可以用动态规划,也可以用贪心和二分
思路
此题就是求一个数组中山脉数组的最大长度。思路很直观,这里直接给出算法:
- 记数组元素个数为 n n n,遍历数组每个元素,对每个元素:
(1) 求出这个元素左边从左到右的最长上升子序列的长度(包含这个元素) l 1 l_1 l1
(2) 求出这个元素右边从右到左的最长上升子序列的长度(包含这个元素) l 2 l_2 l2
(3) 该元素的最长山脉数组长度为 k i = l 1 + l 2 − 1 k_i=l_1+l_2-1 ki=l1+l2−1
(4) 求出所有 k i k_i ki 的最大值 k m a x k_{max} kmax,即为剩下的合唱队形的人数- 输出答案 n − k m a x n-k_{max} n−kmax 即可。
有了思路并不够,还有许多细节需要考虑,比如怎么求解 l 1 l_1 l1 和 l 2 l_2 l2 等。也就是将原问题转为了求最长上升子序列的问题。这里有两种方法:一是动态规划,二是贪心+二分,在这里都可以用,不过动态规划方法的时间复杂度不如后一种方法,所以这里只记录二分法。具体见代码。
代码
#include <limits.h>
#include <stdio.h>
#define N 105
// 二分法找出数组中第一个大于等于目标值的下标,没有则返回数组长度
int first_GE(int* arr, int n, int target) {
int l = 0, r = n - 1, mid = 0;
while (l <= r) {
mid = ((r - l) >> 1) + l;
if (arr[mid] >= target) {
if (!mid || arr[mid - 1] < target) {
return mid;
} else {
r = mid - 1;
}
} else {
l = mid + 1;
}
}
return n;
}
// 求一段数组的最长上升子序列长度
int get_len(int* arr, int start, int end, int* aux, int n, int flag) {
if (start == end) return 1;
int i = 0, j = 0, len = 0;
for (i = 0; i < n; i++) {
aux[i] = INT_MAX;
}
if (flag) {
// 从左往右求最大上升子序列长度
for (i = start; i <= end; i++) {
j = first_GE(aux, n, arr[i]);
if (j > len) len = j;
aux[j] = arr[i];
}
} else {
// 从右往左求最大上升子序列长度
for (i = end; i >= start; i--) {
j = first_GE(aux, n, arr[i]);
if (j > len) len = j;
aux[j] = arr[i];
}
}
return len + 1;
}
int main(void) {
int n = 0, a[N], b[N], i = 0;
scanf("%d", &n);
for (i = 0; i < n; i++) {
scanf("%d", a + i);
}
int ans = 0, t = 0;
for (i = 0; i < n; i++) {
t = get_len(a, 0, i, b, N, 1) + get_len(a, i, n - 1, b, N, 0) - 1;
if (t > ans) ans = t;
}
printf("%d\n", n - ans);
return 0;
}