題目描述:給定一個數組 nums
和滑動窗口的大小 k
,請找出所有滑動窗口裏的最大值。
示例:
輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7]
解釋:
滑動窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
解法 1:暴力法
這題其實暴力法時間效率也很高,直接移動這個滑動窗口,每次統計窗口中的最大值即可。
代碼實現:
// ac地址:https://leetcode-cn.com/problems/sliding-window-maximum/
// 原文地址:https://xxoo521.com/2020-03-27-max-sliding-window/
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (k <= 1) return nums;
const res = [];
for (let i = 0; i < nums.length - k + 1; ++i) {
res.push(Math.max(...nums.slice(i, i + k)));
}
return res;
};
時間複雜度是\(O(kN)\),其中 k 是滑動窗口的長度。空間複雜度是\(O(N)\)。
解法 2: 動態規劃
官方題解中講的很清楚了。
這裏簡單說下重要的點:將數組分成大小相等的塊,每個塊都可以理解爲有兩個數組 left 和 right。left 方向從左到右,right 相反。left[i]
是指塊從開始到下標 i 的最大元素,right[j]
是指塊從開始到下標 j 的最大元素。
假設滑動窗口的範圍是[i, j]
,很容易看出來,滑動窗口中的最大值就是 max(right[i], left[j])
。
代碼實現如下:
// ac地址:https://leetcode-cn.com/problems/sliding-window-maximum/
// 原文地址:https://xxoo521.com/2020-03-27-max-sliding-window/
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (k === 1) return nums;
const length = nums.length;
if (!length) return [];
const left = new Array(length);
const right = new Array(length);
left[0] = nums[0];
right[length - 1] = nums[length - 1];
for (let i = 1; i < length; ++i) {
if (i % k) {
left[i] = Math.max(nums[i], left[i - 1]);
} else {
left[i] = nums[i];
}
let j = length - i - 1;
if ((j + 1) % k) {
right[j] = Math.max(nums[j], right[j + 1]);
} else {
right[j] = nums[j];
}
}
const res = [];
for (let i = 0; i < length - k + 1; i++) {
res.push(Math.max(right[i], left[i + k - 1]));
}
return res;
};
這種做法時間複雜度比解法 1 更低,是\(O(N)\)。
解法 3: 雙端隊列
官方用動畫演示了算法過程。
這裏記錄下重要的點:
- 雙端隊列中保存的是元素下標,方便判斷元素是否在當前滑動窗口中
- 雙端隊列頭元素對應的數字,就是當前滑動窗口的最大值
- 雙端隊列頭尾出入元素的時間複雜度是\(O(1)\)
- 本題的雙端隊列用到功能,用鏈表就可以滿足。C++的 STL 中的雙端隊列支持 insert,考慮了拷貝的高效型,實現上更復雜
爲了方便,代碼使用數組來模擬雙端隊列。代碼實現如下:
// ac地址:https://leetcode-cn.com/problems/sliding-window-maximum/
// 原文地址:https://xxoo521.com/2020-03-27-max-sliding-window/
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (k === 0) return [];
const length = nums.length;
if (length === 0) return [];
const deque = [];
for (let i = 0; i < k; ++i) {
cleanDeque(deque, nums, i, k);
deque.push(i);
}
const res = [];
res.push(nums[deque[0]]);
for (let i = k; i < length; ++i) {
cleanDeque(deque, nums, i, k);
deque.push(i);
res.push(nums[deque[0]]);
}
return res;
};
/**
* 刷新雙端隊列
* @param {number[]} queue 雙端隊列
* @param {number[]} nums 數組
* @param {number} idx 當前元素下標
* @param {number} k 滑動窗口大小
*/
function cleanDeque(queue, nums, idx, k) {
// 如果雙向隊列中,包含不是滑動窗口內的數,直接出隊
if (queue.length && idx >= k + queue[0]) {
queue.shift();
}
while (queue.length && nums[idx] > nums[queue[queue.length - 1]]) {
queue.pop();
}
}
由於每個元素只有 1 次機會進出雙端隊列,所以時間複雜度是\(O(N)\)。
更多資料
整理不易,若對您有幫助,請給個「關注+點贊」,您的支持是我更新的動力 👇
- 📖Blog:劍指 Offer 題解 + JS 代碼
- 🐱Github :https://github.com/dongyuanxin/blog
- 🌟 公衆號:心譚博客