我认为它更像是一种技术,而不是一种算法。这是一种可用于各种算法的技术。
我认为通过以下示例可以最好地理解该技术。想象一下我们有这个数组:
[ 5, 7, 1, 4, 3, 6, 2, 9, 2 ]
我们如何找到五个连续元素的最大和?好吧,我们首先查看5, 7, 1, 4, 3 并看到总和是20。然后我们将查看下一组五个连续元素,即7, 1, 4, 3, 6。这些总和是21。这比我们之前的总和还要多,所以7, 1, 4, 3, 6 是目前我们迄今为止得到的最好的。
让我们看看我们是否可以改进。 1, 4, 3, 6, 2?不,总和为16。 4, 3, 6, 2, 9?总和为24,所以现在这是我们得到的最佳序列。现在我们进入下一个序列,3, 6, 2, 9, 2。这个总和为22,并没有超过我们目前最好的24。我们已经到了尽头,所以我们完成了。
以编程方式实现此功能的蛮力方法如下:
const getMaxSumOfFiveContiguousElements = (arr) => {
let maxSum = -Infinity;
let currSum;
for (let i = 0; i <= arr.length - 5; i++) {
currSum = 0;
for (let j = i; j < i + 5; j++) {
currSum += arr[j];
}
maxSum = Math.max(maxSum, currSum);
}
return maxSum;
};
这个的时间复杂度是多少?这是O(n*k)。外层循环遍历n - k + 1 项目,但是当n 比k 大得多时,我们可以忘记k + 1 部分,而将其称为n 项目。然后内部循环通过k 项目,所以我们有O(n*k)。尝试像这样可视化它:
我们可以将其简化为 O(n) 吗?让我们回到这个数组:
[ 5, 7, 1, 4, 3, 6, 2, 9, 2 ]
首先我们得到5, 7, 1, 4, 3 的总和。接下来我们需要7, 1, 4, 3, 6 的总和。像这样可视化它,每组五个元素都有一个“窗口”。
第一个窗口和第二个窗口有什么区别?嗯,第二个窗口去掉了左边的5,但在右边添加了一个6。因此,既然我们知道第一个窗口的总和是20,为了得到第二个窗口的总和,我们取20,减去5,并加上6,得到21。我们实际上不必遍历第二个窗口中的每个元素并将它们相加 (7 + 1 + 4 + 3 + 6)。这将涉及重复和不必要的工作。
这里的滑动窗口方法最终是两个操作而不是五个,因为k 是5。这并不是一个巨大的改进,但您可以想象,对于更大的k(和更大的n)它确实有帮助。
以下是使用滑动窗口技术的代码的工作方式:
const getLargestSumOfFiveConsecutiveElements = (arr) => {
let currSum = getSum(arr, 0, 4);
let largestSum = currSum;
for (let i = 1; i <= arr.length - 5; i++) {
currSum -= arr[i - 1]; // subtract element to the left of curr window
currSum += arr[i + 4]; // add last element in curr window
largestSum = Math.max(largestSum, currSum);
}
return largestSum;
};
const getSum = (arr, start, end) => {
let sum = 0;
for (let i = start; i <= end; i++) {
sum += arr[i];
}
return sum;
};
这就是滑动窗口技术的要点。在其他问题中,您可能会做一些比获取窗口内元素的总和更复杂的事情。或者窗口本身的大小可能不同,而不是我们在这里看到的固定大小的五个。但是滑动窗口技术的这个基本应用应该为您提供一个可以构建的基础。