[算法]给出一个数组,算出出现在一个固定大小的滑动窗口中的最大值的结果集

本文介绍了一种基于滑动窗口实现的最大值算法,通过维护一个双端队列来跟踪窗口内最大值,适用于处理数据流中连续子数组的最大值问题。
<?php
$arr = [1,4,5,8, 100, 20, 30 , -1, 9, 6, 70, 35, 48, 90];

function maxSlidingWindow($arr, $k){
	if(!$arr) return [];
	
	$window = []; $res = [];
	foreach($arr as $index => $val){
        //由于窗口的移动,window内的元素下标已不在窗口的范围,则移出去
		if($index>=$k && $window[0]<=$index-$k)
			array_shift($window);
		
        //如果window的最后一个下标所对应的元素没有当前元素大,则移出去
		while($window && $arr[$window[count($window)-1]] <= $val)
			array_pop($window);
		
        //将当前下标推入window队列
		array_push($window, $index);
		
        //如果窗口已经满了,则提出最大值
		if($index >= $k-1)
			$res[] = $arr[$window[0]];
	}
	
	return $res;
}

print_r(maxSlidingWindow($arr, 3));

 

我们要解决的问题是: **给定一个长度为 $ n $ 的序列和一个整数 $ k $,找出最长的连续子数组(子序列),使得该子数组中的最大值与最小值之差不超过 $ k $。** 注意:题目中“序列”通常指**连续子数组**(即子串),如果允许非连续,则是子序列问题,但那种情况下贪心更复杂。我们这里按最常见的理解:**求最长的连续子数组,满足 `max - min <= k`**。 --- ### ✅ 解法思路 我们可以使用 **滑动窗口(双指针) + 双端队列(deque)维护窗口内的最大值和最小值** 来高效解决问题。 - 使用两个双端队列: - `maxq`:维护当前窗口中可能成为最大值的元素下标(单调递减) - `minq`:维护当前窗口中可能成为最小值的元素下标(单调递增) - 左右指针 `l`, `r` 枚举滑动窗口 - 每次扩展右边界,加入新元素后,检查 `max - min > k`,如果是,则收缩左边界 - 记录过程中满足条件的最大长度 时间复杂度:$ O(n) $,每个元素最多进出队列一次 空间复杂度:$ O(n) $ --- ### ✅ C++ 代码实现 ```cpp #include <iostream> #include <vector> #include <deque> #include <algorithm> using namespace std; int longestSubarray(vector<int>& nums, int k) { int n = nums.size(); deque<int> maxq; // 单调递减,存下标,用于获取最大值 deque<int> minq; // 单调递增,存下标,用于获取最小值 int l = 0, max_len = 0; for (int r = 0; r < n; ++r) { // 维护 maxq: 保证递减 while (!maxq.empty() && nums[maxq.back()] <= nums[r]) { maxq.pop_back(); } maxq.push_back(r); // 维护 minq: 保证递增 while (!minq.empty() && nums[minq.back()] >= nums[r]) { minq.pop_back(); } minq.push_back(r); // 当前窗口 [l, r] 的 max - min > k,需要收缩左边界 while (nums[maxq.front()] - nums[minq.front()] > k) { if (maxq.front() == l) maxq.pop_front(); if (minq.front() == l) minq.pop_front(); l++; } // 更新最大长度 max_len = max(max_len, r - l + 1); } return max_len; } // 主函数示例 int main() { int n, k; cin >> n >> k; vector<int> arr(n); for (int i = 0; i < n; ++i) { cin >> arr[i]; } cout << longestSubarray(arr, k) << endl; return 0; } ``` --- ### 🔍 代码解释 - `maxq.front()` 始终是当前窗口最大值的索引 - `minq.front()` 始终是当前窗口最小值的索引 - 我们通过 `while` 循环在添加新元素时维护单调性 - 当 `max - min > k` 时,移动左指针 `l++`,并从队列中移除已失效的索引 - 每次合法窗口都尝试更新 `max_len` --- ### 📌 示例输入输出 **输入:** ``` 6 2 4 2 3 1 5 6 ``` **输出:** ``` 4 ``` **解释:** 子数组 `[2,3,1,5]`(索引1~4)最大值=5,最小值=1,差为4 > 2 ❌ 但 `[4,2,3,1]` 最大=4,最小=1 → 差=3 > 2 ❌ `[2,3,1]` 差=2,✅ 长度3 更好的:`[3,1,5]` 差=4 ❌ 实际上 `[4,2,3]`:max=4,min=2 → 差=2 ✅ 长度3 但 `[2,3,1]` 同样 ✅ 最长可能是 `[2,3,1]` 或 `[4,2]` 等等 再算一下:`[3,1]` 差=2 ✅,长度2 其实 `[2,3,1]` 是符合的?max=3, min=1 → 差=2 ✅ 长度3 有没有更长? 试试 `[4,2,3,1]`: max=4, min=1 → 3 > 2 ❌ 所以不行。 但如果数据是 `[1,2,3,4]`, k=3 → max-min=3 ✅,长度4 所以算法正确性依赖于滑动窗口动态调整。 --- ### ✅ 相关优化说明 - 如果不要求连续子数组(即可以是非连续子序列),那么最优策略是排序后用双指针找最长区间使得 `a[j] - a[i] <= k`,但这不是本题重点(因为原题说“序列”,一般默认连续) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值