「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
题目介绍
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 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],这就是最后剩下那块石头的重量。
提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000
解题思路
从题目的要求来看,是需要取一堆数据中的最值的问题,这类问题我们可以用堆(优先队列)来解决,我们也可以用数组排序的方式来解决,以下是两种方法的解题过程
思路一:数组排序
利用数组排序的步骤如下:
- 将
stones
数组按从小到大进行排序 - 从
stones
数组尾部依次弹出数组的最大值和次大值 - 对这两个值进行比较,如果两个值相等,说明 最重的 两块石头完全粉碎,不需要做任何操作;如果两个值不相等,将较大的值减去较小的值,然后将它们的差值插入到数组中,然后对数组重新进行排序
- 重复以上
2-3
的步骤,如果最后stones
数组中只剩下一块石头,返回该石头的重量;如果最后stones
数组为空,直接返回0
解题代码
var lastStoneWeight = function(stones) {
stones.sort((a, b) => a - b)
while (stones.length > 1) {
const one = stones.pop()
const two = stones.pop()
if (one !== two) {
stones.push(one - two)
stones.sort((a, b) => a - b)
}
}
return stones.length ? stones.pop() : 0
};
思路二:大顶堆
堆结构是解决最值问题的一个比较好的数据结构,题目要求是取出石头的最大值,因此我们使用大顶堆
根据题目的要求,我们可以判断出这个堆结构需要 push
(插入元素)、pop
(弹出元素)、sortBack
(从堆的尾部向上调整堆结构)、sortFront
(从堆顶元素向下调整堆结构)、size
(堆的大小)这 5
个方法
利用大顶堆的步骤如下:
- 定义一个大顶堆类
Heap
,类中包含上述五个方法 - 创建一个堆实例
heap
,将stones
数组中的元素依次插入到大顶堆中去,在插入的过程中需要维护大顶堆的结构 - 如果大顶堆的大小大于
1
,弹出堆顶元素(最大值),向下调整大顶堆的结构,弹出堆顶元素(次大值) - 比较两个值的大小关系,如果一样大,则不做任何操作;如果不一样大,将两者相减的差值插入到大顶堆中,然后向上调整大顶堆的结构
- 重复
3-4
的过程,直到堆中元素的数量小于等于1
- 如果最后堆中还有一个元素,返回该元素;否则直接返回
0
解题代码
var lastStoneWeight = function(stones) {
// 创建一个堆实例
const heap = new Heap()
// 将数组中的元素依次插入堆中
while (stones.length) heap.push(stones.pop())
// 如果堆中元素数量大于1,弹出最大的两个值进行比较
while (heap.size() > 1) {
let one = heap.pop()
let two = heap.pop()
if (one !== two) heap.push(Math.abs(one - two))
}
// 如果最后堆中元素数量不为0,返回堆中的元素,否则返回0
return heap.size() ? heap.pop() : 0
};
class Heap {
constructor() {
this.arr = []
}
// 堆的大小
size () {
return this.arr.length
}
// 弹出堆顶元素
pop() {
const val = this.arr[0]
const back = this.arr.pop()
// 如果弹出尾部元素之后,堆的大小不为0,那么将尾部元素放置到堆顶的位置
if (this.size()) {
this.arr[0] = back
}
// 向下调整
this.sortFront()
return val
}
// 插入元素
push(val) {
this.arr.push(val)
// 向上调整
this.sortBack()
}
// 向上调整
sortBack() {
let ind = this.arr.length - 1
// 如果没到堆顶元素并且子节点的值大于父节点的值,交换两个节点的位置
while (ind > 0 && this.arr[ind] > this.arr[Math.floor((ind - 1) / 2)]) {
[this.arr[ind], this.arr[Math.floor((ind - 1) / 2)]] = [this.arr[Math.floor((ind - 1) / 2)], this.arr[ind]]
ind = Math.floor((ind - 1) / 2)
}
}
// 向下调整
sortFront() {
if (!this.size()) return
let ind = 0
// 如果当前节点有子节点
while (ind * 2 + 1 < this.arr.length) {
// temp 为当前节点及其子节点中最大值的位置
let temp = ind
if (this.arr[ind * 2 + 1] > this.arr[ind]) temp = ind * 2 + 1
// 左孩子存在不代表右孩子也存在,因此需要先判断是否有右孩子
if (this.arr[ind * 2 + 2] !== undefined && this.arr[ind * 2 + 2] > this.arr[temp]) temp = ind * 2 + 2
// 如果当前节点就是最大值,不需要交换位置
if (temp === ind) break
// 交换当前节点与最大值的位置
[this.arr[ind], this.arr[temp]] = [this.arr[temp], this.arr[ind]]
ind = temp
}
}
}