树形结构-对应算法详解四

138 阅读7分钟

文章简要概述

  • 本文主要进行树相关的算法题刷题题解记录,记录树相关算法以及如何解。
  • 这文一共有5道题,主要介绍leetcode中剑指 Offer 40. 最小的k个数1046. 最后一块石头的重量703. 数据流中的第 K 大元素373. 查找和最小的 K 对数字215. 数组中的第K个最大元素的解题思路。

与树相关算法

剑指 Offer 40. 最小的k个数

剑指 Offer 40. 最小的k个数 -- leetcode

题目大意:

输入整数数组 `arr` ,找出其中最小的 `k` 个数。例如,输入451627388个数字,则最小的4个数字是1234

示例

输入: arr = [3,2,1], k = 2

输出: [1,2] 或者 [2,1]

解题思路:

  • 暴力解法,把数组先排序,再取出最小的4个数
  • 建立一个小顶堆的树形结构,在树的根节点存放的就是整颗树最小的值
  • 遍历k,一次取出树的根节点,然后调整整颗树的值,再次取树根节点,知道超出k
  • 这样就可以得到结果值,大家可以搜索小顶堆或优先队列了解概念

代码:

// 交换节点
function swap(arr, i, child) {
    if (i === child) return;
    arr[i] = arr[child] + arr[i];
    arr[child] = arr[i] - arr[child];
    arr[i] = arr[i] - arr[child];
}
// 父子节点比较,子节点比较小就交换位置
function heapAdjust(arr, i, len) {
    let child = i * 2 + 1;
    while (child < len) {
        if (child + 1 < len && arr[child] > arr[child + 1]) {
            child = child + 1;
        }
        if (arr[child] < arr[i]) {
            swap(arr, i, child);
            i = child;
            child = i * 2 + 1;
        } else {
            break;
} }
}
// 遍历处理每一个数据
function buildHeap (arr) {
    let len = arr.length;
    for (let i = Math.floor(len / 2); i >= 0; i--) {
        heapAdjust(arr, i, len);
    }
}
/**
 * @param {number[]} arr
 * @param {number} k
 * @return {number[]}
 */
var getLeastNumbers = function(arr, k) {
  let len = arr.length;
    let res = [];
    if(k===0)return [];
    if(k===len)return arr;
    buildHeap(arr);
    for (let i = 1; i <= k; i++) {
        res.push(arr[0]);
        swap(arr, 0, len - i);
        heapAdjust(arr, 0, len - i);
    }
    return res;
};

1046. 最后一块石头的重量

1046. 最后一块石头的重量 -- leetcode

题目大意:

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

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 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,取出前两位元素进行比较,讲剩余值放入大顶堆中
  • 最后比较大顶堆是否为空,有值输出值,否则输出0

代码:

/**
 * @param {number[]} stones
 * @return {number}
 */
var lastStoneWeight = function(stones) {
  const maxHeap = new MaxPriorityQueue();
  for(let i = 0; i< stones.length; i++) {
      maxHeap.enqueue('x', stones[i]);
  }
  while(maxHeap.size() > 1) {
      const a = maxHeap.dequeue()['priority'];
      const b = maxHeap.dequeue()['priority'];
      if (a > b) {
          maxHeap.enqueue('x', a - b);
      }
  }
  return maxHeap.isEmpty() ? 0 : maxHeap.dequeue()['priority'];
};

703. 数据流中的第 K 大元素

703. 数据流中的第 K 大元素 -- leetcode

题目大意:

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:

["KthLargest", "add", "add", "add", "add", "add"]

[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]

输出:

[null, 4, 5, 5, 8, 8]

解释: KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);

kthLargest.add(3); // return 4

kthLargest.add(5); // return 5

kthLargest.add(10); // return 5

kthLargest.add(9); // return 8

kthLargest.add(4); // return 8

解题思路:

  • 借助小顶堆的数据结构,先封装一个小顶堆结构。
  • 封装一个类,讲初始值放入到小顶堆中
  • 封装一个add方法,讲值添加到小顶堆中,并返回小顶堆中堆顶元素,即可实现

代码:

/**
 * @param {number} k
 * @param {number[]} nums
 */
var KthLargest = function(k, nums) {
  this.k = k;
  this.minHeap = new MinHeap();
  for(num of nums) {
      this.add(num);
  }
};
/** 
 * @param {number} val
 * @return {number}
 */
KthLargest.prototype.add = function(val) {
    this.minHeap.add(val);
    if(this.minHeap.size() > this.k) {
        this.minHeap.pop();
    }
    return this.minHeap.peek();
}
class MinHeap {
    constructor(data = []) {
      this.data = data;
      this.heapfiy()
    }
    heapfiy() {
        if (this.size() < 2) return;
        for(i = 1; i < this.size(); i++) {
            this.bobbleUp(i);
        }
    }
    add (val) {
        this.data.push(val);
        this.bobbleUp(this.size() - 1);
    }
    pop () {
        if (this.size() === 0) return null;
        const res = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            this.data[0] = last;
            this.bobbleDown(0);
        }
        return res;
    }
    peek () {
        if (this.size() === 0) return null;
        return this.data[0];
    }
    bobbleDown(index) {
        const lastIndex = this.size() - 1;
        while(true) {
            const leftChild = 2 * index + 1;
            const rightChild = 2 * index + 2;
            let swapIndex = index;
            if (leftChild <= lastIndex && this.compare(this.data[swapIndex], this.data[leftChild]) > 0) {
                swapIndex = leftChild;
            }
            if (rightChild <= lastIndex && this.compare(this.data[swapIndex], this.data[rightChild]) > 0) {
                swapIndex = rightChild;
            }
            if (swapIndex !== index) {
                this.swap(swapIndex, index);
                index = swapIndex;
            } else {
                break;
            }
        }
    }
    bobbleUp(index) {
       while(index > 0) {
           const parentIndex = (index - 1) >> 1;
           if (this.compare(this.data[parentIndex], this.data[index]) > 0) {
               this.swap(parentIndex, index);
               index = parentIndex;
           } else {
               break;
           }
       }
    }
    compare(a, b) {
       return a - b;
    }
    size() {
        return this.data.length;
    }
    swap(i1, i2) {
      [this.data[i1], this.data[i2]] = [this.data[i2], this.data[i1]];
    }
}

373. 查找和最小的 K 对数字

373. 查找和最小的 K 对数字 -- leetcode

题目大意:

给定两个以 升序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。

请找到和最小的 k 个数对 (u1,v1),  (u2,v2)  ...  (uk,vk) 。

示例:

输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3

输出: [1,2],[1,4],[1,6]

解释:

返回序列中的前 3 对数:

[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

解题思路:

  • 遍历两个数组的中的数据,讲两个数组的组合值放入到大顶堆中
  • 大顶堆中存入组合值的合
  • 当大顶堆中的元素超过k个,讲最大的元素删除
  • 最后大顶堆中留下的数据就是答案

代码:

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @param {number} k
 * @return {number[][]}
 */
var kSmallestPairs = function (nums1, nums2, k) {
    const pq = new MaxPriorityQueue({ priority: t => t[0] + t[1] });
    for (const num1 of nums1) {
        for (const num2 of nums2) {
            const item = [num1, num2]
            pq.enqueue(item);
            if (pq.size() > k) {
                if (pq.dequeue().element === item) {
                    break;
                }
            }
        }
    }
    return pq.toArray().map(i => i.element);
};

215. 数组中的第K个最大元素

215. 数组中的第K个最大元素 -- leetcode

题目大意:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例:

输入: [3,2,1,5,6,4] 和 k = 2

输出: 5

解题思路:

  • 借助小顶堆的结构实现
  • 讲初始值放入到小顶堆中
  • 当堆的容量超过k时,就删除堆顶
  • 插入结束后,堆顶就是第k个最大元素

代码:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
class MinHeap {
    constructor() {
        this.heap = [];
    }
    // 交换节点位置
    swap(i1, i2) {
        [this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
    }
    // 获得父节点
    getParentIndex(i) {
        return (i - 1) >> 1;
    }
    // 获得左节点
    getleftIndex(i) {
        return 2 * i + 1;
    }
    // 获得右节点
    getrightIndex(i) {
        return 2 * i + 2;
    }
    // 上移
    shiftUp(index) {
        if (index === 0) return;

        const parentIndex = this.getParentIndex(index);
        if (this.heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    // 下移
    shiftDown(index) {
        const leftIndex = this.getleftIndex(index);
        const rightIndex = this.getrightIndex(index);
        if (this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if (this.heap[rightIndex] < this.heap[index]) {
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    // 插入
    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    // 删除堆顶
    pop() {
        // pop()方法删除数组最后一个元素并返回,赋值给堆顶
        this.heap[0] = this.heap.pop();
        // 对堆顶重新排序
        this.shiftDown(0);
    }
    // 获取堆顶
    peek() {
        return this.heap[0];
    }
    // 获取堆的大小
    size() {
        return this.heap.length;
    }
}

const findKthLargest = (nums, k) => {
    const minHeap = new MinHeap();
    nums.forEach(n => {
        // 将数组元素依次插入堆中
        minHeap.insert(n);
        // 如果堆大小超过k, 开始裁员, 将堆顶(最小) 的去掉
        if (minHeap.size() > k) {
            minHeap.pop();
        }
    })
    // 返回堆顶,此时就是第k大的元素
    return minHeap.peek();
};

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是树部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

树形结构-对应算法详解一                             树形结构-对应算法详解二                             链表相关算法复习一

链表相关算法复习二                                      链表相关算法复习三                                      数据结构与算法-栈一

数据结构与算法-栈二                                    数据结构与算法-队列一                                数据结构与算法-队列二

数据结构与算法-链表一                                 数据结构与算法-链表二                                 数据结构与算法-链表三

有兴趣的可以一起来刷题,求点赞👍 关注!