文章简要概述
- 本文主要进行树相关的算法题刷题题解记录,记录树相关算法以及如何解。
- 这文一共有5道题,主要介绍leetcode中
剑指 Offer 40. 最小的k个数、1046. 最后一块石头的重量、703. 数据流中的第 K 大元素、373. 查找和最小的 K 对数字和215. 数组中的第K个最大元素的解题思路。
与树相关算法
剑指 Offer 40. 最小的k个数
剑指 Offer 40. 最小的k个数 -- leetcode
题目大意:
输入整数数组 `arr` ,找出其中最小的 `k` 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例
输入: 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. 最后一块石头的重量
题目大意:
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 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 大元素
题目大意:
设计一个找到数据流中第 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 对数字
题目大意:
给定两个以 升序排列 的整数数组 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个最大元素
题目大意:
给定整数数组 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。往期文章:
树形结构-对应算法详解一 树形结构-对应算法详解二 链表相关算法复习一
链表相关算法复习二 链表相关算法复习三 数据结构与算法-栈一
数据结构与算法-栈二 数据结构与算法-队列一 数据结构与算法-队列二
数据结构与算法-链表一 数据结构与算法-链表二 数据结构与算法-链表三
有兴趣的可以一起来刷题,求点赞👍 关注!