两种解法:1、按条件暴力搜索+剪枝 2、滑动窗口+双端队列维护最值的索引
1、两层循环,嘎嘎暴力,剪枝思路: (1)nums[i]范围不满足,直接开启下一轮循环; (2)从索引i开始的剩余元素数量,比当前最长稳定序列小,和整个循环说“告辞”; (3) 如果稳定字符串因为下一个值范围越界而结束,[i + 1, end]一定比原来[i, end]小,所以i直接从 end后面开启循环。 如果稳定字符串因为区间最值之差大于4而结束,则i要从i+1就重新进入循环,如:[18, 20, 21, 22, 24]两个最长稳定子序列[0,3]和[1,4].
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int N;
cin >> N;
vector<int> nums(N);
for (int i = 0; i < N; ++i) {
cin >> nums[i];
}
int maxSteadyLength = 1;
vector<pair<int, int>> res;
for (int i = 0; i < N;) {
if (nums[i] < 18 || nums[i] > 24) {
++i;
continue;
}
if (N - i < maxSteadyLength) break;
int start = i, end = i;
int tempMin = nums[i], tempMax = nums[i];
int tempLength = 1;
// tempMax - tempMin <= 4 判断下一轮增加长度后的窗口是否满足稳定性,提前终止无意义遍历
for (int j = i; tempMax - tempMin <= 4 && nums[j + 1] <= 24 &&
nums[j + 1] >= 18 && j < N - 1; ++j) {
tempMax = max(tempMax, nums[j + 1]);
tempMin = min(tempMin, nums[j + 1]);
// 判断新增元素后是否能构成有效稳定子数组,只有校验通过才更新子数组最大长度
if (tempMax - tempMin <= 4) {
tempLength++;
maxSteadyLength = max(maxSteadyLength, tempLength);
end = j + 1;
} else {
end = j;
}
}
res.emplace_back(start, end);
i = (nums[end + 1] <= 24 && nums[end + 1] >= 18) ? i + 1 : end + 2;
}
for(auto [s, e] : res){
if (maxSteadyLength == e - s + 1) {
cout << s << " " << e << endl;
}
}
return 0;
}
2、滑动窗口 (1)对比上面思路,相当于:每轮循环j不用从i开始重新记录满足条件最值(j不走回头路,用双端队列记录最值情况)i右移缩小窗口,第一次满足max-min <=4时,[i, j]长度一定最大。 (2)最大/小值索引队列(为什么不存普通最值?):可以天然存储多个相同最值,当left右移时方便确定nums[left]是否对最值队列产生影响,如果有则移除该影响。
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
int main() {
int N;
cin >> N;
vector<int> nums(N);
for (int i = 0; i < N; ++i) {
cin >> nums[i];
}
int maxSteadyLength = 1;
vector> res;
deque<int> max_queue; // 队首存储窗口最大值索引
deque<int> min_queue; // 队首存储窗口最小值索引
int left = 0;
for(int right = 0; right < N; ++right){
// 对于当前nums[right]与队尾元素比较,维护最大/小值索引队列
while(!max_queue.empty() && nums[max_queue.back()] < nums[right]){
max_queue.pop_back();
}
max_queue.push_back(right);
while(!min_queue.empty() && nums[min_queue.back() ]> nums[right]){
min_queue.pop_back();
}
min_queue.push_back(right);
// 如果nums[right]不在稳定范围内[18, 24],清空最大/小值索引队列
if(nums[right] < 18 || nums[right] > 24){
max_queue.clear();
min_queue.clear();
left = right + 1;
continue;
}
// 向右收缩left,确保窗口内的最值差<=4,并清空nums[left]对最大/小值索引队列的影响
while(!max_queue.empty() && !min_queue.empty()){
int curr_max = nums[max_queue.front()];
int curr_min = nums[min_queue.front()];
if(curr_max - curr_min > 4){
// 这里要做索引判断!!!
if(max_queue.front() == left){
max_queue.pop_front();
}
if(min_queue.front() == left){
min_queue.pop_front();
}
left++;
}else {
break;
}
}
// 计算当前窗口大小,符合条件结果加入结果集
int curr_length = right - left + 1;
if(curr_length > maxSteadyLength){
maxSteadyLength = curr_length;
res.clear();
res.emplace_back(left, right);
}else if (curr_length == maxSteadyLength) {
res.emplace_back(left, right);
}
}
for(auto [s, e] : res){
if (maxSteadyLength == e - s + 1) {
cout << s << " " << e << endl;
}
}
return 0;
}