牛客4810合唱队:队列变换的最优解法

一、 问题理解
合唱队形要求队伍中有一个"峰顶",使得峰顶左侧的同学身高严格递增,峰顶右侧的同学身高严格递减。我们的目标是在不改变同学相对位置的前提下,找出最少需要出列多少同学,使剩余同学满足这一条件。
二、解题思路
这个问题可以转化为寻找一个位置i,使得:
i左侧是一个严格递增序列
i右侧是一个严格递减序列
这样的序列长度尽可能长
这样,最少出列人数 = 总人数 - 最长合唱队形长度
三、动态规划解法
我们使用两个动态规划数组:
left[i]:表示以heights[i]结尾的最长递增子序列长度
right[i]:表示以heights[i]开头的最长递减子序列长度
对于每个位置i,合唱队形的最大长度为left[i] + right[i] - 1(减去重复计算的i位置)
四、算法步骤详解
初始化两个数组left和right,所有元素初始值为1
从左到右计算left数组:
对于每个i,检查前面所有比heights[i]小的元素j
更新left[i] = max(left[i], left[j] + 1)
从右到左计算right数组:
对于每个i,检查后面所有比heights[i]小的元素j
更新right[i] = max(right[i], right[j] + 1)
遍历所有位置,计算最大合唱队形长度
最少出列人数 = 总人数 - 最大合唱队形长度
五、完整代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/**
* 计算最少需要出列的同学数量,使剩余同学形成合唱队形
* @param heights 同学身高数组
* @return 最少需要出列的同学数量
*/
int minStudentsToRemove(vector<int>& heights) {
int n = heights.size();
if (n <= 2) return 0; // 少于3人不需要出列
vector<int> left(n, 1); // 从左到右的最长递增子序列长度
vector<int> right(n, 1); // 从右到左的最长递减子序列长度
// 计算从左到右的最长递增子序列
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (heights[j] < heights[i]) {
left[i] = max(left[i], left[j] + 1);
}
}
}
// 计算从右到左的最长递减子序列
for (int i = n - 2; i >= 0; --i) {
for (int j = n - 1; j > i; --j) {
if (heights[j] < heights[i]) {
right[i] = max(right[i], right[j] + 1);
}
}
}
int max_keep = 0;
// 计算每个位置作为峰值的最大保留人数
for (int i = 0; i < n; ++i) {
// 左右两边至少各1人才能形成合唱队形
if (left[i] >= 1 && right[i] >= 1) {
max_keep = max(max_keep, left[i] + right[i] - 1);
}
}
return n - max_keep;
}
int main() {
int n;
cin >> n;
vector<int> heights(n);
for (int i = 0; i < n; ++i) {
cin >> heights[i];
}
cout << minStudentsToRemove(heights) << endl;
return 0;
}六、优化思路
对于n较大的情况(接近3000),可以考虑使用二分查找优化最长递增子序列的计算,将时间复杂度降低到O(nlogn)。
七、实际应用
这种算法不仅适用于合唱队形问题,还可以应用于其他需要寻找序列中特定模式的问题,如股票分析中的峰值检测、信号处理中的波形分析等。
原创内容 转载请注明出处
