[路飞]_最后一块石头的重量

573 阅读3分钟

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

leetcode-1046 最后一块石头的重量
b站视频

题目介绍

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

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 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 <= stones.length <= 30
  • 1 <= stones[i] <= 1000

解题思路

从题目的要求来看,是需要取一堆数据中的最值的问题,这类问题我们可以用堆(优先队列)来解决,我们也可以用数组排序的方式来解决,以下是两种方法的解题过程

思路一:数组排序

利用数组排序的步骤如下:

  1. stones 数组按从小到大进行排序
  2. stones 数组尾部依次弹出数组的最大值和次大值
  3. 对这两个值进行比较,如果两个值相等,说明 最重的 两块石头完全粉碎,不需要做任何操作;如果两个值不相等,将较大的值减去较小的值,然后将它们的差值插入到数组中,然后对数组重新进行排序
  4. 重复以上 2-3 的步骤,如果最后 stones 数组中只剩下一块石头,返回该石头的重量;如果最后 stones 数组为空,直接返回 0

最后一块石头的重量.gif

解题代码

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 个方法

利用大顶堆的步骤如下:

  1. 定义一个大顶堆类 Heap,类中包含上述五个方法
  2. 创建一个堆实例 heap,将 stones 数组中的元素依次插入到大顶堆中去,在插入的过程中需要维护大顶堆的结构
  3. 如果大顶堆的大小大于 1,弹出堆顶元素(最大值),向下调整大顶堆的结构,弹出堆顶元素(次大值)
  4. 比较两个值的大小关系,如果一样大,则不做任何操作;如果不一样大,将两者相减的差值插入到大顶堆中,然后向上调整大顶堆的结构
  5. 重复 3-4 的过程,直到堆中元素的数量小于等于 1
  6. 如果最后堆中还有一个元素,返回该元素;否则直接返回 0

最后一块石头的重量-大顶堆.gif

解题代码

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 
        }
    }
}