「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」
题目
1046. 最后一块石头的重量
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎; 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
先选出 7 和 8,得到 1,所以数组转换为 [2,4,1,1,1],
再选出 2 和 4,得到 2,所以数组转换为 [2,1,1,1],
接着是 2 和 1,得到 1,所以数组转换为 [1,1,1],
最后选出 1 和 1,得到 0,最终数组转换为 [1],这就是最后剩下那块石头的重量。
解法
大顶堆
思路
这题是典型的大顶堆应用案例。
操作步骤如下:
- 维护一个大顶堆
- 每次取堆顶元素两次,然后计算,如果有余数,则再放回堆内
- 继续重复上述操作,直到堆内元素只剩一个或者不剩。
只不过对于javascript来说,稍微吃亏一点,需要手写大顶堆哈哈。
没事,我们就再手写一次,加深印象。
手写堆注意事项:
- 有默认defaultCmp比较函数,支持传入cmp,这样可以大小顶堆复用
- 推入元素,让元素和父元素比较(父元素下标为Math.floor((x-1)/2),这个在纸上画一颗完全二叉树,根节点下标为0,比划一下就能得到了)。根据我们传入的比较函数来比较,这题是大顶堆,那么如果父元素更小,则两者交换,继续递归去比较。
- 取出堆顶元素,先备份堆顶元素用于返回出去,再取出末尾元素,放到堆顶,然后让该元素向下浮动,和子元素比较。
- 如果只有一个子元素,且子元素更大,则交换
- 如果有两个子元素,哪个更大则跟哪个交换
代码
/**
* @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的数组来存放大顶堆。