leetcode刷题_1046. 最后一块石头的重量

106 阅读1分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战

题目

1046. 最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎; 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

示例:

输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 78,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 24,得到 2,所以数组转换为 [2,1,1,1],
接着是 21,得到 1,所以数组转换为 [1,1,1],
最后选出 11,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。

解法

大顶堆

思路

这题是典型的大顶堆应用案例。

操作步骤如下:

  1. 维护一个大顶堆
  2. 每次取堆顶元素两次,然后计算,如果有余数,则再放回堆内
  3. 继续重复上述操作,直到堆内元素只剩一个或者不剩。

只不过对于javascript来说,稍微吃亏一点,需要手写大顶堆哈哈。

没事,我们就再手写一次,加深印象。

手写堆注意事项:

  1. 有默认defaultCmp比较函数,支持传入cmp,这样可以大小顶堆复用
  2. 推入元素,让元素和父元素比较(父元素下标为Math.floor((x-1)/2),这个在纸上画一颗完全二叉树,根节点下标为0,比划一下就能得到了)。根据我们传入的比较函数来比较,这题是大顶堆,那么如果父元素更小,则两者交换,继续递归去比较。
  3. 取出堆顶元素,先备份堆顶元素用于返回出去,再取出末尾元素,放到堆顶,然后让该元素向下浮动,和子元素比较。
    • 如果只有一个子元素,且子元素更大,则交换
    • 如果有两个子元素,哪个更大则跟哪个交换

代码

/**
 * @param {number[]} stones
 * @return {number}
 */
var lastStoneWeight = function(stones) {
    let maxHeap = new Heap();
    for(let i=0;i<stones.length;i++){
        maxHeap.offer(stones[i]);
    }
    while(maxHeap.size()>1){
        let x = maxHeap.poll();
        let y = maxHeap.poll();
        if(x != y){
            maxHeap.offer(Math.abs(x - y))
        }
    }
    return maxHeap.size() ? maxHeap.poll() : 0
};

let defaultCmp = (a,b) => a>b;
class Heap {
    constructor(cmp = defaultCmp ){
        this.cmp = cmp;
        this.heap = [];
    }
    size(){
        return this.heap.length;
    }
    offer(node){
        this.heap.push(node);
        this.siftUp(this.size()-1);
    }
    poll(){
        let node = this.heap[0];
        let lastNode = this.heap.pop();
        if(this.size()>0){
            this.heap[0] = lastNode
            this.siftDown(0);
        }
        return node
    }
    siftUp(x){
        let parent = Math.floor((x-1)/2);
        if(parent >= 0 && this.cmp(this.heap[x],this.heap[parent])){
            [this.heap[x], this.heap[parent]] = [this.heap[parent], this.heap[x]];
            x = parent;
            this.siftUp(x);
        }
    }
    siftDown(x){
        let child = 2*x + 1;
        if(child <= this.size()-1){
            if(child+1<=this.size()-1 && this.cmp(this.heap[child+1],this.heap[child])){
                child = child+1;
            }
            if(this.cmp(this.heap[child],this.heap[x])){
                [this.heap[x],this.heap[child]] = [this.heap[child],this.heap[x]]
                x = child;
                this.siftDown(x);   
            }
        }
    }
}


复杂度分析

时间复杂度:O(nlogn), 元素个数为n,我们一次取两个,最坏情况,每次取两个都要放回一个,复杂度为O(n), 大顶堆向下浮动复杂度为O(logn),故总共为O(nlogn)

空间复杂度:O(n),需要一个长度为n的数组来存放大顶堆。