LeetCode 之 JavaScript 解答第69题 —— X 的平方根(Squrt(x))


Time:2019/4/16
Title: Sliding Window Maximum
Difficulty: Difficulty
Author: 小鹿


题目:Sliding Window Maximum

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

Example:

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7] 
Explanation: 

Window position                Max
---------------               -----
[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

Note:
You may assume k is always valid, 1 ≤ k ≤ input array's size for non-empty array.

Follow up:
Could you solve it in linear time?

Solve:

▉ 问题分析

暴力破解法

1)看到这个题目最容易想到的就是暴力破解法,借助一个 for 循环,两个变量,整体移动窗口,然后每移动一次就在大小为 k 的窗口求出最大值。

2)但是这样的解决效率非常低,如果数据非常大时,共有 n1 个数据,窗口大小为 n2(n1 远远大于 n2),时间复杂度为 n2(n1 - n2) 。也就是 n1 * n2,最坏时间复杂度为 n^2。

优先级队列

1)每次移动窗口求最大值,以及在动态数据中求最大值,我们想到的就是优先级队列,而优先级队列的实现是堆这种数据结构,这道题用堆解决效率更高。如果对堆不熟悉,赶紧给自己补补功课吧!底部有我写的文章链接。

2)通过堆的优化,向堆中插入数据时间复杂度为 logn ,所以时间复杂度为 nlogn。

▉ 算法思路

暴力破解法:

1)用两个指针,分别指向窗口的起始位置和终止位置,然后遍历窗口中的数据,求出最大值;向前移动两个指针,然后操作,直到遍历数据完成位置。

优先级队列:

1)需要维护大小为 k 的大顶堆,堆顶就是当前窗口最大的数据,当移动窗口时,如果插入的数据大于堆顶的数据,将其加入到结果集中。同时要删除数据,如果删除的数据为最大数据且插入的数据小于删除的数据时,向大小为 k 的以 logn 的时间复杂度插入,返回堆顶元素。

▉ 暴力破解法

var maxSlidingWindow = function(nums, k) {
    if(k > nums.length || k === 0) return [];
    let res = [], maxIndex = -1;
    for(let l = 0, r = k-1;r < nums.length;l++, r++){
        if(maxIndex < l){
            // 遍历求出最大值
            let index = l;
            for(let i = l;i <= r;i++) {
                if(nums[i] > nums[index]) index = i;
            }
            maxIndex = index;
        }
        if(nums[r] > nums[maxIndex]){
            maxIndex = r;
        }
        res.push(nums[maxIndex]);
    }
    return res;
};
▉ 优先级队列
let count = 0;
let heap = [];
let n = 0;
var maxSlidingWindow = function(nums, k) {
    let pos = k;
    n = k;
    let result = [];
    let len = nums.length;

    // 判断数组和最大窗口树是否为空
    if(nums.length === 0 || k === 0) return result;

    // 建大顶堆
    let j = 0
    for(;j < k; j++){
        insert(nums[j]);
    }
    result.push(heap[1]); 

    // 移动窗口
    while(len - pos > 0){
        if(nums[k] > heap[1]){
            result.push(nums[k]);
            insert(nums[k]);
            nums.shift();
            pos++; 
        }else{
            if(nums.shift() === heap[1]){
                removeMax(); 
            }
            insert(nums[k-1]);
            result.push(heap[1]);
            pos++;
        }
    }
    return result;
};  



// 插入数据
const insert = (data) =>{
    //判断堆满
    // if(count >= n) return; // >=

    // 插入到数组尾部
    count++
    heap[count] = data;

    //自下而上堆化
    let i = count;
    while(i / 2 > 0 && heap[i] > heap[parseInt(i/2)]){
        swap(heap,i,parseInt(i/2));
        i = parseInt(i/2);
    }
}

// 两个数组内元素交换
swap = (arr,x,y) =>{
    let temp = arr[x];
    arr[x] = arr[y];
    arr[y] = temp;
}

// 堆的删除
const removeMax = () =>{
    // 判断堆空
    if(count <= 0) return ;

    // 最大数据移到最后删除
    heap[1] = heap[count];

    // 长度减一
    count--;
    // 删除数据
    heap.pop();

    // 从上到下堆化
    heapify(heap,count,1);
}

// 从上到下堆化
const heapify = (heap,count,i) =>{
    while(true){
        // 存储堆子节点的最大值下标
        let maxPos = i;

        // 左子节点比父节点大
        if(i*2 < n && heap[i*2] > heap[i]) maxPos = i*2;
        // 右子节点比父节点大
        if(i*2+1 <= n && heap[i*2+1] > heap[maxPos]) maxPos = i*2+1;

        // 如果没有发生替换,则说明该堆只有一个结点(父节点)或子节点都小于父节点
        if(maxPos === i) break;

        // 交换
        swap(heap,maxPos,i);
        // 继续堆化
        i = maxPos;
    }
}
▉ 性能分析

暴力破解法

  • 时间复杂度:O(n^2).
  • 空间复杂度:O(1).

优先级队列

  • 时间复杂度:nlogn.
  • 空间复杂度:O(1).
▉ 扩展

堆:

1)堆插入、删除操作

2)如何实现一个堆?

3)堆排序

4)堆的应用

详细查看写的另一篇关于堆的文章:数据结构与算法之美【堆】


欢迎一起加入到 LeetCode 开源 Github 仓库,可以向 me 提交您其他语言的代码。在仓库上坚持和小伙伴们一起打卡,共同完善我们的开源小仓库!
Github:https://github.com/luxiangqiang/JS-LeetCode

欢迎关注我个人公众号:「一个不甘平凡的码农」,记录了自己一路自学编程的故事。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容