问题描述
在处理数组(或 LinkedList)的许多问题中,我们被要求在给定大小的所有子数组(或子列表)中查找或计算某些东西。例如,看看这个问题:
给定一个数组,求其中所有“K”个连续元素的子数组的平均值。
让我们用一个真实的输入来理解这个问题:
Array: [1, 3, 2, 6, -1, 4, 1, 8, 2], K=5
在这里,我们被要求找到给定数组中“5”个连续元素的所有子数组的平均值。让我们解决这个问题:
对于前 5 个数字(索引 0-4 的子数组),平均值为:(1 + 3 + 2 + 6-1) / 5 => 2.2
接下来 5 个数字(索引 1-5 的子数组)的平均值为:(3 + 2 + 6-1 + 4) / 5 => 2.8
对于接下来的 5 个数字(索引 2-6 的子数组),平均值为:(2 + 6-1 + 4 + 1) / 5 => 2.4
这是包含所有大小为 5 的子数组的平均值的最终输出:
Output: [2.2, 2.8, 2.4, 3.6, 2.8]
若我们暴力算法将计算给定数组的每个 5 元素子数组的总和,并将总和除以“5”以求平均值。
时间复杂度:由于对于输入数组的每个元素,我们都在计算其下一个“K”个元素的总和,因此上述算法的时间复杂度将为 O (N * K)
O (N * K),其中“N”是输入数组中的元素数。
我们能找到更好的解决方案吗?您是否发现上述方法效率低下?
效率低下的是,对于任何两个大小为“5”的连续子数组,重叠部分(将包含四个元素)将被计算两次。例如,采用上述输入:
如你所见,子数组(索引从 0-4)和子数组(索引从 1-5)之间有四个重叠元素。我们可以以某种方式重用我们为重叠元素计算的总和吗?
解决此问题的有效方法是将每个子数组可视化为“5”个元素的滑动窗口。这意味着当我们移动到下一个子数组时,我们将窗口滑动一个元素。重用前一个子数组的总和这将使我们免于遍历整个子数组来求和,因此,算法复杂度将降低到 O (N)。
这是滑动窗口方法最基本的解题模板:
int slidingwindow(int K, vector<int>& arr) {
vector<double> result(arr.size() - K + 1);
int windowSum = 0;
int windowStart = 0;//窗口的头部
for (int windowEnd = 0; windowEnd < arr.size(); windowEnd++) {
if ()//关键在于窗口收缩的时机,判断逻辑while或者if
{//窗口收缩一般是指窗口头部向前移动,此时要更新与窗口头部相关的数据结构如map等}
}
return result;
}
问题的关键
1.本类问题的代码关键是根据题目得出窗口收缩的时机,以及想清楚窗口收缩后应该干什么
2.窗口的增大一般是用for循环来向后走
3.同时也要考虑需不需要其他数据结构辅助保存一些需要的数据