跳到主要内容

19 篇博文 含有标签「堆(优先队列)」

查看所有标签

· 阅读需 3 分钟

1、题干

现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, ...]

实现 SmallestInfiniteSet 类:

  • SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含 所有 正整数。
  • int popSmallest() 移除 并返回该无限集中的最小整数。
  • void addBack(int num) 如果正整数 num 存在于无限集中,则将一个 num 添加 到该无限集最后。

 

示例:

输入
["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"]
[[], [2], [], [], [], [1], [], [], []]
输出
[null, null, 1, 2, 3, null, 1, 4, 5]

解释
SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet();
smallestInfiniteSet.addBack(2); // 2 已经在集合中,所以不做任何变更。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 2 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 3 ,并将其从集合中移除。
smallestInfiniteSet.addBack(1); // 将 1 添加到该集合中。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 在上一步中被添加到集合中,
// 且 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 4 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 5 ,并将其从集合中移除。

 

提示:

  • 1 <= num <= 1000
  • 最多调用 popSmallestaddBack 方法 共计 1000

2、思路

两个思路:

  • 优先队列:直接用优先队列存取数据即可,但要注意数字不能重复,总体最差时间复杂度 O(nlogn)O(n\log{n})
  • 暴力:由于数据范围比较小,可以使用计数排序的思路存取数据进行暴力求解,总体最差时间复杂度 O(n2)O(n^2)

3、代码

优先队列

const N = 1001;
class SmallestInfiniteSet {
nums = new Array(N).fill(1);
minQueue = new MinPriorityQueue();
constructor() {
for (let i = 1; i < N; i++) {
this.minQueue.enqueue(i);
}
}

popSmallest(): number {
const n = this.minQueue.dequeue().element;
this.nums[n] = 0;
return n;
}

addBack(num: number): void {
if (this.nums[num]) return;
this.nums[num] = 1;
this.minQueue.enqueue(num);
}
}

暴力

class SmallestInfiniteSet {
nums: number[] = [];
constructor() {
this.nums = new Array(1001).fill(1);
}

popSmallest(): number {
const k = this.nums.findIndex((n, i) => i > 0 && n > 0);
this.nums[k] = 0;
return k;
}

addBack(num: number): void {
this.nums[num] = 1;
}
}

· 阅读需 3 分钟

1、题干

给你一个非负整数数组 nums 。在一步操作中,你必须:

  • 选出一个正整数 xx 需要小于或等于 nums最小非零 元素。
  • nums 中的每个正整数都减去 x

返回使 nums 中所有元素都等于 0 需要的 最少 操作数。

 

示例 1:

输入:nums = [1,5,0,3,5]
输出:3
解释:
第一步操作:选出 x = 1 ,之后 nums = [0,4,0,2,4] 。
第二步操作:选出 x = 2 ,之后 nums = [0,2,0,0,2] 。
第三步操作:选出 x = 2 ,之后 nums = [0,0,0,0,0] 。

示例 2:

输入:nums = [0]
输出:0
解释:nums 中的每个元素都已经是 0 ,所以不需要执行任何操作。

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

2、思路1

根据题目描述,每一步都消除了所有最小的正整数,因此最少步数就是数组 nums 去重后正整数的个数。

第一个思路是:排序后统计去重正整数个数

3、代码

function minimumOperations(nums: number[]): number {
nums.sort((a, b) => a - b);

let step = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] && nums[i] !== nums[i - 1]) step++;
}

return step;
};

4、复杂度

  • 时间复杂度:O(nlogn)O(n \log n)
  • 空间复杂度:O(logn)O(\log n)

5、执行结果

image.png

6、思路2

哈希去重统计正整数的个数

7、代码

function minimumOperations(nums: number[]): number {
const set = new Set(nums);
return set.size - (set.has(0) ? 1 : 0);
};

或者这样

function minimumOperations(nums: number[]): number {
return new Set(nums.filter(Boolean)).size;
};

8、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

9、执行结果

image.png

· 阅读需 4 分钟

1、题干

一所学校里有一些班级,每个班级里有一些学生,现在每个班都会进行一场期末考试。给你一个二维数组 classes ,其中 classes[i] = [passi, totali] ,表示你提前知道了第 i 个班级总共有 totali 个学生,其中只有 passi 个学生可以通过考试。

给你一个整数 extraStudents ,表示额外有 extraStudents 个聪明的学生,他们 一定 能通过任何班级的期末考。你需要给这 extraStudents 个学生每人都安排一个班级,使得 所有 班级的 平均 通过率 最大 。

一个班级的 通过率 等于这个班级通过考试的学生人数除以这个班级的总人数。平均通过率 是所有班级的通过率之和除以班级数目。

请你返回在安排这 extraStudents 个学生去对应班级后的 最大 平均通过率。与标准答案误差范围在 10-5 以内的结果都会视为正确结果。

 

示例 1:

输入:classes = [[1,2],[3,5],[2,2]], extraStudents = 2
输出:0.78333
解释:你可以将额外的两个学生都安排到第一个班级,平均通过率为 (3/4 + 3/5 + 2/2) / 3 = 0.78333 。

示例 2:

输入:classes = [[2,4],[3,9],[4,5],[2,10]], extraStudents = 4
输出:0.53485

 

提示:

  • 1 <= classes.length <= 105
  • classes[i].length == 2
  • 1 <= passi <= totali <= 105
  • 1 <= extraStudents <= 105

2、思路

这题思路比较容易想到,先用优先队列存储各个班级通过率 classes,优先队列排序逻辑是:假设这个班引入1个逢考必过的学霸 extraStudents 后通过率增长越多排序越靠前。

接着每次安排1个学霸进入通过率增长最多的班级,计算该班级通过率增长值并累加到最初的总通过率 rate,最后求平均就能得到最大平均通过率。

3、代码

function maxAverageRatio(classes: number[][], c: number): number {
const pq = new PriorityQueue({
compare: (a: number[], b: number[]): number => {
const da = (a[0] + 1) / (a[1] + 1) - a[0] / a[1];
const db = (b[0] + 1) / (b[1] + 1) - b[0] / b[1];
return db - da;
}
});

let rate = 0;
for (let i = 0; i < classes.length; i++) {
pq.enqueue(classes[i]);
rate += classes[i][0] / classes[i][1];
}

for (; c; c--) {
const t = pq.dequeue();
pq.enqueue([t[0] + 1, t[1] + 1]);
rate += (t[0] + 1) / (t[1] + 1) - t[0] / t[1];
}

return rate / classes.length;
};

4、复杂度

  • 时间复杂度:O(nlogn)O(nlogn)
  • 时间复杂度:O(n)O(n)

5、执行结果

image.png

· 阅读需 3 分钟

1、题干

现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2不同 类型的水或者 1 杯任意类型的水。

给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]amount[1]amount[2] 分别表示需要装满冷水、温水和热水的杯子数量。返回装满所有杯子所需的 最少 秒数。

 

示例 1:

输入:amount = [1,4,2]
输出:4
解释:下面给出一种方案:
第 1 秒:装满一杯冷水和一杯温水。
第 2 秒:装满一杯温水和一杯热水。
第 3 秒:装满一杯温水和一杯热水。
第 4 秒:装满一杯温水。
可以证明最少需要 4 秒才能装满所有杯子。

示例 2:

输入:amount = [5,4,4]
输出:7
解释:下面给出一种方案:
第 1 秒:装满一杯冷水和一杯热水。
第 2 秒:装满一杯冷水和一杯温水。
第 3 秒:装满一杯冷水和一杯温水。
第 4 秒:装满一杯温水和一杯热水。
第 5 秒:装满一杯冷水和一杯热水。
第 6 秒:装满一杯冷水和一杯温水。
第 7 秒:装满一杯热水。

示例 3:

输入:amount = [5,0,0]
输出:5
解释:每秒装满一杯冷水。

 

提示:

  • amount.length == 3
  • 0 <= amount[i] <= 100

2、思路

假设杯子数量从少到多分别为 minmidmax,首先得装满最多的杯子,因此存在两种情况:

  • min + mid <= max,这种情况只需要 max 秒钟就能装满所有杯子
  • min + mid > max,先装满最多的杯子,需要 max 秒钟
    • 另外两种杯子会剩余 min + mid - max 个,剩余杯子可能为奇数也可能为偶数,但两种杯子差值最多为 1,所以还需要 Math.ceil((mid + min - max) / 2) 秒钟

3、代码

function fillCups(amount: number[]): number {
const [min, mid, max] = amount.sort((a, b) => a - b);
if (min + mid <= max) return max;
return max + Math.ceil((mid + min - max) / 2);
};

4、复杂度

  • 时间复杂度:O(1)O(1)
  • 空间复杂度:O(1)O(1)

5、执行结果

image.png

· 阅读需 6 分钟

1、题干

给你两个整数 m 和 k ,以及数据流形式的若干整数。你需要实现一个数据结构,计算这个数据流的 MK 平均值 。

MK 平均值 按照如下步骤计算:

  1. 如果数据流中的整数少于 m 个,MK 平均值 为 -1 ,否则将数据流中最后 m 个元素拷贝到一个独立的容器中。
  2. 从这个容器中删除最小的 k 个数和最大的 k 个数。
  3. 计算剩余元素的平均值,并 向下取整到最近的整数 。

请你实现 MKAverage 类:

  • MKAverage(int m, int k) 用一个空的数据流和两个整数 m 和 k 初始化 MKAverage 对象。
  • void addElement(int num) 往数据流中插入一个新的元素 num 。
  • int calculateMKAverage() 对当前的数据流计算并返回 MK 平均数 ,结果需 向下取整到最近的整数

 

示例 1:

输入:
["MKAverage", "addElement", "addElement", "calculateMKAverage", "addElement", "calculateMKAverage", "addElement", "addElement", "addElement", "calculateMKAverage"]
[[3, 1], [3], [1], [], [10], [], [5], [5], [5], []]
输出:
[null, null, null, -1, null, 3, null, null, null, 5]

解释:
MKAverage obj = new MKAverage(3, 1);
obj.addElement(3); // 当前元素为 [3]
obj.addElement(1); // 当前元素为 [3,1]
obj.calculateMKAverage(); // 返回 -1 ,因为 m = 3 ,但数据流中只有 2 个元素
obj.addElement(10); // 当前元素为 [3,1,10]
obj.calculateMKAverage(); // 最后 3 个元素为 [3,1,10]
// 删除最小以及最大的 1 个元素后,容器为 [3]
// [3] 的平均值等于 3/1 = 3 ,故返回 3
obj.addElement(5); // 当前元素为 [3,1,10,5]
obj.addElement(5); // 当前元素为 [3,1,10,5,5]
obj.addElement(5); // 当前元素为 [3,1,10,5,5,5]
obj.calculateMKAverage(); // 最后 3 个元素为 [5,5,5]
// 删除最小以及最大的 1 个元素后,容器为 [5]
// [5] 的平均值等于 5/1 = 5 ,故返回 5

 

提示:

  • 3 <= m <= 105
  • 1 <= k*2 < m
  • 1 <= num <= 105
  • addElement 与 calculateMKAverage 总操作次数不超过 105 次。

这10分真的十分难拿,临场肯定写不出来,没有第三方库肯定也写不出来

2、思路

3棵二叉搜索树保存最近 m 个数字,其中 trees[0] 保存最小的 k 个数、trees[1] 保存中间的 m - 2*k 个数、trees[2] 保存最大的 k 个数。保存中间数字之和 sum,并在添加元素时更新,以便快速计算平均值。

addElement 时先进行移除操作,再进行插入操作:

  • 若当前已超过 m 个数,则从树中移除倒数第 m+1 个数,然后将处于后方树中最小的数字往前方的树填充
  • 将当前数字插入第一棵树,然后将前方树中多余的最大数字往后方的树填充

calculateMKAverage 直接使用中间数字之和 sum 求平均

3、代码

var MKAverage = function (m, k) {
this.m = m;
this.k = k;

this.nums = [];
this.trees = [new AVLTree(), new AVLTree(), new AVLTree()];
this.sum = 0;
};

MKAverage.prototype.addElement = function (num) {
this.nums.push(num);
const trees = this.trees;

// remove
if (this.nums.length > this.m) {
const n = this.nums[this.nums.length - this.m - 1];
let i = trees.findIndex(t => t.contains(n));

if (i === 1) this.sum -= n;
trees[i].remove(n);

for (; i < trees.length - 1 && trees[i + 1].size > 0; i++) {
const tmin = trees[i + 1].min();
trees[i + 1].remove(tmin);
trees[i].insert(tmin);
if (i === 0) this.sum -= tmin;
else if (i === 1) this.sum += tmin;
}
}

// insert
for (let i = 0; i < trees.length; i++) {
const c = i !== 1 ? this.k : this.m - 2 * this.k;
trees[i].insert(num);
if (i === 1) this.sum += num;

if (trees[i].size <= c) break;

num = trees[i].max();
trees[i].remove(num);
if (i === 1) this.sum -= num;
}
};

MKAverage.prototype.calculateMKAverage = function () {
return this.nums.length < this.m ? -1 : (this.sum / (this.m - 2 * this.k)) >> 0;
};

// https://github.com/w8r/avl/
function print(root,printNode){if(printNode===void 0)printNode=function(n){return n.key};var out=[];row(root,'',true,function(v){return out.push(v)},printNode);return out.join('')}function row(root,prefix,isTail,out,printNode){if(root){out((""+prefix+(isTail?'└── ':'├── ')+(printNode(root))+"\n"));var indent=prefix+(isTail?' ':'│ ');if(root.left){row(root.left,indent,false,out,printNode)}if(root.right){row(root.right,indent,true,out,printNode)}}}function isBalanced(root){if(root===null){return true}var lh=height(root.left);var rh=height(root.right);if(Math.abs(lh-rh)<=1&&isBalanced(root.left)&&isBalanced(root.right)){return true}return false}function height(node){return node?(1+Math.max(height(node.left),height(node.right))):0}function loadRecursive(parent,keys,values,start,end){var size=end-start;if(size>0){var middle=start+Math.floor(size/2);var key=keys[middle];var data=values[middle];var node={key:key,data:data,parent:parent};node.left=loadRecursive(node,keys,values,start,middle);node.right=loadRecursive(node,keys,values,middle+1,end);return node}return null}function markBalance(node){if(node===null){return 0}var lh=markBalance(node.left);var rh=markBalance(node.right);node.balanceFactor=lh-rh;return Math.max(lh,rh)+1}function sort(keys,values,left,right,compare){if(left>=right){return}var pivot=keys[(left+right)>>1];var i=left-1;var j=right+1;while(true){do{i++}while(compare(keys[i],pivot)<0);do{j--}while(compare(keys[j],pivot)>0);if(i>=j){break}var tmp=keys[i];keys[i]=keys[j];keys[j]=tmp;tmp=values[i];values[i]=values[j];values[j]=tmp}sort(keys,values,left,j,compare);sort(keys,values,j+1,right,compare)}function DEFAULT_COMPARE(a,b){return a>b?1:a<b?-1:0}function rotateLeft(node){var rightNode=node.right;node.right=rightNode.left;if(rightNode.left){rightNode.left.parent=node}rightNode.parent=node.parent;if(rightNode.parent){if(rightNode.parent.left===node){rightNode.parent.left=rightNode}else{rightNode.parent.right=rightNode}}node.parent=rightNode;rightNode.left=node;node.balanceFactor+=1;if(rightNode.balanceFactor<0){node.balanceFactor-=rightNode.balanceFactor}rightNode.balanceFactor+=1;if(node.balanceFactor>0){rightNode.balanceFactor+=node.balanceFactor}return rightNode}function rotateRight(node){var leftNode=node.left;node.left=leftNode.right;if(node.left){node.left.parent=node}leftNode.parent=node.parent;if(leftNode.parent){if(leftNode.parent.left===node){leftNode.parent.left=leftNode}else{leftNode.parent.right=leftNode}}node.parent=leftNode;leftNode.right=node;node.balanceFactor-=1;if(leftNode.balanceFactor>0){node.balanceFactor-=leftNode.balanceFactor}leftNode.balanceFactor-=1;if(node.balanceFactor<0){leftNode.balanceFactor+=node.balanceFactor}return leftNode}var AVLTree=function AVLTree(comparator,noDuplicates){if(noDuplicates===void 0)noDuplicates=false;this._comparator=comparator||DEFAULT_COMPARE;this._root=null;this._size=0;this._noDuplicates=!!noDuplicates};var prototypeAccessors={size:{configurable:true}};AVLTree.prototype.destroy=function destroy(){return this.clear()};AVLTree.prototype.clear=function clear(){this._root=null;this._size=0;return this};prototypeAccessors.size.get=function(){return this._size};AVLTree.prototype.contains=function contains(key){if(this._root){var node=this._root;var comparator=this._comparator;while(node){var cmp=comparator(key,node.key);if(cmp===0){return true}else if(cmp<0){node=node.left}else{node=node.right}}}return false};AVLTree.prototype.next=function next(node){var successor=node;if(successor){if(successor.right){successor=successor.right;while(successor.left){successor=successor.left}}else{successor=node.parent;while(successor&&successor.right===node){node=successor;successor=successor.parent}}}return successor};AVLTree.prototype.prev=function prev(node){var predecessor=node;if(predecessor){if(predecessor.left){predecessor=predecessor.left;while(predecessor.right){predecessor=predecessor.right}}else{predecessor=node.parent;while(predecessor&&predecessor.left===node){node=predecessor;predecessor=predecessor.parent}}}return predecessor};AVLTree.prototype.forEach=function forEach(callback){var current=this._root;var s=[],done=false,i=0;while(!done){if(current){s.push(current);current=current.left}else{if(s.length>0){current=s.pop();callback(current,i++);current=current.right}else{done=true}}}return this};AVLTree.prototype.range=function range(low,high,fn,ctx){var Q=[];var compare=this._comparator;var node=this._root,cmp;while(Q.length!==0||node){if(node){Q.push(node);node=node.left}else{node=Q.pop();cmp=compare(node.key,high);if(cmp>0){break}else if(compare(node.key,low)>=0){if(fn.call(ctx,node)){return this}}node=node.right}}return this};AVLTree.prototype.keys=function keys(){var current=this._root;var s=[],r=[],done=false;while(!done){if(current){s.push(current);current=current.left}else{if(s.length>0){current=s.pop();r.push(current.key);current=current.right}else{done=true}}}return r};AVLTree.prototype.values=function values(){var current=this._root;var s=[],r=[],done=false;while(!done){if(current){s.push(current);current=current.left}else{if(s.length>0){current=s.pop();r.push(current.data);current=current.right}else{done=true}}}return r};AVLTree.prototype.at=function at(index){var current=this._root;var s=[],done=false,i=0;while(!done){if(current){s.push(current);current=current.left}else{if(s.length>0){current=s.pop();if(i===index){return current}i++;current=current.right}else{done=true}}}return null};AVLTree.prototype.minNode=function minNode(){var node=this._root;if(!node){return null}while(node.left){node=node.left}return node};AVLTree.prototype.maxNode=function maxNode(){var node=this._root;if(!node){return null}while(node.right){node=node.right}return node};AVLTree.prototype.min=function min(){var node=this._root;if(!node){return null}while(node.left){node=node.left}return node.key};AVLTree.prototype.max=function max(){var node=this._root;if(!node){return null}while(node.right){node=node.right}return node.key};AVLTree.prototype.isEmpty=function isEmpty(){return!this._root};AVLTree.prototype.pop=function pop(){var node=this._root,returnValue=null;if(node){while(node.left){node=node.left}returnValue={key:node.key,data:node.data};this.remove(node.key)}return returnValue};AVLTree.prototype.popMax=function popMax(){var node=this._root,returnValue=null;if(node){while(node.right){node=node.right}returnValue={key:node.key,data:node.data};this.remove(node.key)}return returnValue};AVLTree.prototype.find=function find(key){var root=this._root;var subtree=root,cmp;var compare=this._comparator;while(subtree){cmp=compare(key,subtree.key);if(cmp===0){return subtree}else if(cmp<0){subtree=subtree.left}else{subtree=subtree.right}}return null};AVLTree.prototype.insert=function insert(key,data){if(!this._root){this._root={parent:null,left:null,right:null,balanceFactor:0,key:key,data:data};this._size++;return this._root}var compare=this._comparator;var node=this._root;var parent=null;var cmp=0;if(this._noDuplicates){while(node){cmp=compare(key,node.key);parent=node;if(cmp===0){return null}else if(cmp<0){node=node.left}else{node=node.right}}}else{while(node){cmp=compare(key,node.key);parent=node;if(cmp<=0){node=node.left}else{node=node.right}}}var newNode={left:null,right:null,balanceFactor:0,parent:parent,key:key,data:data};var newRoot;if(cmp<=0){parent.left=newNode}else{parent.right=newNode}while(parent){cmp=compare(parent.key,key);if(cmp<0){parent.balanceFactor-=1}else{parent.balanceFactor+=1}if(parent.balanceFactor===0){break}else if(parent.balanceFactor<-1){if(parent.right.balanceFactor===1){rotateRight(parent.right)}newRoot=rotateLeft(parent);if(parent===this._root){this._root=newRoot}break}else if(parent.balanceFactor>1){if(parent.left.balanceFactor===-1){rotateLeft(parent.left)}newRoot=rotateRight(parent);if(parent===this._root){this._root=newRoot}break}parent=parent.parent}this._size++;return newNode};AVLTree.prototype.remove=function remove(key){if(!this._root){return null}var node=this._root;var compare=this._comparator;var cmp=0;while(node){cmp=compare(key,node.key);if(cmp===0){break}else if(cmp<0){node=node.left}else{node=node.right}}if(!node){return null}var returnValue=node.key;var max,min;if(node.left){max=node.left;while(max.left||max.right){while(max.right){max=max.right}node.key=max.key;node.data=max.data;if(max.left){node=max;max=max.left}}node.key=max.key;node.data=max.data;node=max}if(node.right){min=node.right;while(min.left||min.right){while(min.left){min=min.left}node.key=min.key;node.data=min.data;if(min.right){node=min;min=min.right}}node.key=min.key;node.data=min.data;node=min}var parent=node.parent;var pp=node;var newRoot;while(parent){if(parent.left===pp){parent.balanceFactor-=1}else{parent.balanceFactor+=1}if(parent.balanceFactor<-1){if(parent.right.balanceFactor===1){rotateRight(parent.right)}newRoot=rotateLeft(parent);if(parent===this._root){this._root=newRoot}parent=newRoot}else if(parent.balanceFactor>1){if(parent.left.balanceFactor===-1){rotateLeft(parent.left)}newRoot=rotateRight(parent);if(parent===this._root){this._root=newRoot}parent=newRoot}if(parent.balanceFactor===-1||parent.balanceFactor===1){break}pp=parent;parent=parent.parent}if(node.parent){if(node.parent.left===node){node.parent.left=null}else{node.parent.right=null}}if(node===this._root){this._root=null}this._size--;return returnValue};AVLTree.prototype.load=function load(keys,values,presort){if(keys===void 0)keys=[];if(values===void 0)values=[];if(this._size!==0){throw new Error('bulk-load: tree is not empty');}var size=keys.length;if(presort){sort(keys,values,0,size-1,this._comparator)}this._root=loadRecursive(null,keys,values,0,size);markBalance(this._root);this._size=size;return this};AVLTree.prototype.isBalanced=function isBalanced$1(){return isBalanced(this._root)};AVLTree.prototype.toString=function toString(printNode){return print(this._root,printNode)};Object.defineProperties(AVLTree.prototype,prototypeAccessors);

4、复杂度

  • 时间复杂度:addElementO(logn)O(logn)calculateMKAverageO(1)O(1)
  • 空间复杂度:O(n)O(n)

5、执行结果

image.png

· 阅读需 7 分钟

1、题干

给你一个二维整数数组 orders ,其中每个 orders[i] = [pricei, amounti, orderTypei] 表示有 amounti 笔类型为 orderTypei 、价格为 pricei 的订单。

订单类型 orderTypei 可以分为两种:

  • 0 表示这是一批采购订单 buy
  • 1 表示这是一批销售订单 sell

注意,orders[i] 表示一批共计 amounti 笔的独立订单,这些订单的价格和类型相同。对于所有有效的 i ,由 orders[i] 表示的所有订单提交时间均早于 orders[i+1] 表示的所有订单。

存在由未执行订单组成的 积压订单 。积压订单最初是空的。提交订单时,会发生以下情况:

  • 如果该订单是一笔采购订单 buy ,则可以查看积压订单中价格 最低 的销售订单 sell 。如果该销售订单 sell 的价格 低于或等于 当前采购订单 buy 的价格,则匹配并执行这两笔订单,并将销售订单 sell 从积压订单中删除。否则,采购订单 buy 将会添加到积压订单中。
  • 反之亦然,如果该订单是一笔销售订单 sell ,则可以查看积压订单中价格 最高 的采购订单 buy 。如果该采购订单 buy 的价格 高于或等于 当前销售订单 sell 的价格,则匹配并执行这两笔订单,并将采购订单 buy 从积压订单中删除。否则,销售订单 sell 将会添加到积压订单中。

输入所有订单后,返回积压订单中的 订单总数 。由于数字可能很大,所以需要返回对 109 + 7 取余的结果。

 

示例 1:

输入:orders = [[10,5,0],[15,2,1],[25,1,1],[30,4,0]]
输出:6
解释:输入订单后会发生下述情况:
- 提交 5 笔采购订单,价格为 10 。没有销售订单,所以这 5 笔订单添加到积压订单中。
- 提交 2 笔销售订单,价格为 15 。没有采购订单的价格大于或等于 15 ,所以这 2 笔订单添加到积压订单中。
- 提交 1 笔销售订单,价格为 25 。没有采购订单的价格大于或等于 25 ,所以这 1 笔订单添加到积压订单中。
- 提交 4 笔采购订单,价格为 30 。前 2 笔采购订单与价格最低(价格为 15)的 2 笔销售订单匹配,从积压订单中删除这 2 笔销售订单。第 3 笔采购订单与价格最低的 1 笔销售订单匹配,销售订单价格为 25 ,从积压订单中删除这 1 笔销售订单。积压订单中不存在更多销售订单,所以第 4 笔采购订单需要添加到积压订单中。
最终,积压订单中有 5 笔价格为 10 的采购订单,和 1 笔价格为 30 的采购订单。所以积压订单中的订单总数为 6 。

示例 2:

输入:orders = [[7,1000000000,1],[15,3,0],[5,999999995,0],[5,1,1]]
输出:999999984
解释:输入订单后会发生下述情况:
- 提交 109 笔销售订单,价格为 7 。没有采购订单,所以这 109 笔订单添加到积压订单中。
- 提交 3 笔采购订单,价格为 15 。这些采购订单与价格最低(价格为 7 )的 3 笔销售订单匹配,从积压订单中删除这 3 笔销售订单。
- 提交 999999995 笔采购订单,价格为 5 。销售订单的最低价为 7 ,所以这 999999995 笔订单添加到积压订单中。
- 提交 1 笔销售订单,价格为 5 。这笔销售订单与价格最高(价格为 5 )的 1 笔采购订单匹配,从积压订单中删除这 1 笔采购订单。
最终,积压订单中有 (1000000000-3) 笔价格为 7 的销售订单,和 (999999995-1) 笔价格为 5 的采购订单。所以积压订单中的订单总数为 1999999991 ,等于 999999984 % (109 + 7) 。

 

提示:

  • 1 <= orders.length <= 105
  • orders[i].length == 3
  • 1 <= pricei, amounti <= 109
  • orderTypei01

2、思路

根据题意模拟,使用优先队列存储销售订单和采购订单

3、代码

function getNumberOfBacklogOrders(orders: number[][]): number {
const bpq = new PriorityQueue({ compare: (a, b) => b.price - a.price });
const spq = new PriorityQueue({ compare: (a, b) => a.price - b.price });

for (let [price, amount, type] of orders) {
if (type === 0) {
while (spq.size() && amount > 0) {
const o = spq.front();
if (o.price > price) break;
amount -= o.amount;
if (amount >= 0) spq.dequeue();
else o.amount = -amount;
}

if (amount > 0) bpq.enqueue({ price, amount });
} else {
while (bpq.size() && amount > 0) {
const o = bpq.front();
if (o.price < price) break;
amount -= o.amount;
if (amount >= 0) bpq.dequeue();
else o.amount = -amount;
}

if (amount > 0) spq.enqueue({ price, amount });
}
}

let ans = 0, M = 1e9 + 7;
while (bpq.size()) {
const o = bpq.dequeue();
ans = (ans + o.amount) % M;
}

while (spq.size()) {
const o = spq.dequeue();
ans = (ans + o.amount) % M;
}

return ans;
};

4、复杂度

  • 时间复杂度:o(nlogn)o(n*logn)
  • 空间复杂度:o(n)o(n)

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

在考场里,一排有 N 个座位,分别编号为 0, 1, 2, ..., N-1 。

当学生进入考场后,他必须坐在能够使他与离他最近的人之间的距离达到最大化的座位上。如果有多个这样的座位,他会坐在编号最小的座位上。(另外,如果考场里没有人,那么学生就坐在 0 号座位上。)

返回 ExamRoom(int N) 类,它有两个公开的函数:其中,函数 ExamRoom.seat() 会返回一个 int (整型数据),代表学生坐的位置;函数 ExamRoom.leave(int p) 代表坐在座位 p 上的学生现在离开了考场。每次调用 ExamRoom.leave(p) 时都保证有学生坐在座位 p 上。

 

示例:

输入:["ExamRoom","seat","seat","seat","seat","leave","seat"], [[10],[],[],[],[],[4],[]]
输出:[null,0,9,4,2,null,5]
解释:
ExamRoom(10) -> null
seat() -> 0,没有人在考场里,那么学生坐在 0 号座位上。
seat() -> 9,学生最后坐在 9 号座位上。
seat() -> 4,学生最后坐在 4 号座位上。
seat() -> 2,学生最后坐在 2 号座位上。
leave(4) -> null
seat() -> 5,学生最后坐在 5 号座位上。

 

提示:

  1. 1 <= N <= 10^9
  2. 在所有的测试样例中 ExamRoom.seat() 和 ExamRoom.leave() 最多被调用 10^4 次。
  3. 保证在调用 ExamRoom.leave(p) 时有学生正坐在座位 p 上。

2、代码

class ExamRoom {
n = -1;
min = Infinity;
max = -Infinity;
set = new Set<number>();
pq = new PriorityQueue({
compare: (a: [number, number], b: [number, number]): number => {
const da = (a[1] - a[0]) / 2 >> 0, db = (b[1] - b[0]) / 2 >> 0;
return da !== db ? db - da : a[0] - b[0];
}
});

constructor(n: number) {
this.n = n;
}

seat(): number {
let i = 0;
if (this.set.size) {
let found = false;
while (!found && this.pq.size()) {
const [n1, n2] = this.pq.dequeue();
if (n2 - n1 < 1 || !this.set.has(n1) || !this.set.has(n2)) continue;

const [m1, m2] = this.pq.front() || [];
if (n1 === m1 && n2 === m2) continue;

i = (n1 + n2) / 2 >> 0;
if (this.set.has(i) && this.set.has(i + 1)) continue;

if ((n2 - n1) / 2 >> 0 <= this.min || (n2 - n1) / 2 >> 0 < this.n - 1 - this.max) {
this.pq.enqueue([n1, n2]);
break;
}

found = true;
if (this.set.has(i)) i = i + 1;

this.pq.enqueue([n1, i]);
this.pq.enqueue([i, n2]);
}

if (!found) {
if (this.n - 1 - this.max > this.min) {
i = this.n - 1;
this.pq.enqueue([this.max, i]);
} else {
i = 0;
this.pq.enqueue([i, this.min]);
}
}
}

this.set.add(i);
if (i < this.min) this.min = i;
if (i > this.max) this.max = i;
return i;
}

leave(p: number): void {
this.set.delete(p);
const nums = [...this.set].sort((a, b) => a - b);

this.min = !nums.length ? Infinity : nums[0];
this.max = !nums.length ? -Infinity : nums[nums.length - 1];

const i = nums.findIndex(n => n > p);
if (i > 0) this.pq.enqueue([nums[i - 1], nums[i]]);
}
}

3、执行结果

image.png

· 阅读需 5 分钟

1、题干

如果字符串中不含有任何 'aaa''bbb''ccc' 这样的字符串作为子串,那么该字符串就是一个「快乐字符串」。

给你三个整数 abc,请你返回 任意一个 满足下列全部条件的字符串 s

  • s 是一个尽可能长的快乐字符串。
  • s最多a 个字母 'a'b 个字母 'b'c 个字母 'c'
  • s 中只含有 'a''b''c' 三种字母。

如果不存在这样的字符串 s ,请返回一个空字符串 ""

 

示例 1:

输入:a = 1, b = 1, c = 7
输出:"ccaccbcc"
解释:"ccbccacc" 也是一种正确答案。

示例 2:

输入:a = 2, b = 2, c = 1
输出:"aabbc"

示例 3:

输入:a = 7, b = 1, c = 0
输出:"aabaa"
解释:这是该测试用例的唯一正确答案。

 

提示:

  • 0 <= a, b, c <= 100
  • a + b + c > 0

题目对我来说挺有难度,想了蛮久,用了两种思路,都是基于3种字母的数量关系解题,这里记录其中一种

e9de6ffd302988140f868f8f54c96d26.gif


2、解题思路

总体思路

  • 按数量对 'a''b''c' 三种字母进行降序排序
  • 由于快乐字符串中不能出现3个连续相同字符,可以先取数量最多的字母,每两个组成一个字符串并存入数组中,记为 arr
  • 遍历 arr 中的所有字符串,把剩余字母逐个拼接到每个字符串末尾,由于剩余任意一种字母数量都不会超过 2arr.length2*arr.length,因此任意一种字母必定可以在两轮循环内消耗完,且 arr 中任意一个字符串都不会出现3个连续相同字符
  • 最后拼接所有字符串,只可能在末尾出现3个以上连续相同字符,把末尾多余字符处理掉即可

举个例子a = 1, b = 1, c = 7

  • 1、取数量最多的字母 c,每两个组成一个字符串,得到:['cc','cc','cc','c']
  • 2、把剩余字母 a 逐个拼接到每个字符串末尾,得到:['cca','cc','cc','c'],此时一轮循环尚未结束
  • 3、一轮循环没结束,字母 a 已耗尽,继续该轮循环,把剩余字母 b 逐个拼接到每个字符串末尾,得到:['cca','ccb','cc','c']
  • 4、拼接所有字符串再处理末尾多余字符,得到:'ccaccbcc'

再举个例子a = 6, b = 6, c = 6

  • 1、取数量最多的字母 a,每两个组成一个字符串,得到:['aa','aa','aa']
  • 2、把剩余字母 b 逐个拼接到每个字符串末尾,得到:['aab','aab','aab'],此时已结束一轮循环
  • 3、一轮循环没有消耗所有字母 b,再来一轮,得到:['aabb','aabb','aabb'],此时已结束两轮循环
  • 4、同理把剩余字母 c 逐个拼接到每个字符串末尾,得到:['aabbcc','aabbcc','aabbcc']
  • 5、拼接所有字符串再处理末尾多余字符,得到:'aabbccaabbccaabbcc

3、代码

var longestDiverseString = function (a, b, c) {
const m = [['a', a], ['b', b], ['c', c]].sort((a, b) => b[1] - a[1]);

const len = Math.ceil(m[0][1] / 2);
const arr = new Array(len).fill(m[0][0] + m[0][0]);
if (m[0][1] % 2) arr[len - 1] = m[0][0];

for (let i = 0; m[1][1] || m[2][1]; i++) {
if (m[1][1]) arr[i % len] += m[1][0], m[1][1]--;
else arr[i % len] += m[2][0], m[2][1]--;
}

return arr.join('').replace(new RegExp(m[0][0] + '{3,}$'), m[0][0] + m[0][0]);
};

4、执行结果

image.png

· 阅读需 3 分钟

1、题干

给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。

 

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

 

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

 

进阶:所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

 

注意:本题与主站 347 题相同:https://leetcode-cn.com/problems/top-k-frequent-elements/

2、解法1-API排序

先使用哈希映射 map 对所有整数进行计数,然后将 map 中的键值对存入数组并使用排序API按数量降序排序,最后返回前 k 个数字


3、代码

var topKFrequent = function (nums, k) {
const map = new Map();
for (const n of nums) map.set(n, 1 + (map.get(n) || 0));

const arr = [];
for (const kv of map) arr.push(kv);
return arr.sort((a, b) => b[1] - a[1]).slice(0, k).map(a => a[0]);
};

4、复杂度

  • 时间复杂度:O(nlogn)O(nlogn)
  • 空间复杂度:O(n)O(n)

5、解法2-桶排序

先使用哈希映射 map 对所有整数进行计数,然后将 map 中的键值对存入按数量存入桶中,最后返回前 k 个数字。其时间复杂度为 O(n)O(n),解决了进阶问题。


6、代码

var topKFrequent = function (nums, k) {
const map = new Map();
for (const n of nums) map.set(n, 1 + (map.get(n) || 0));

const buckets = new Array(nums.length + 1);
for (const [n, v] of map) !buckets[v] ? buckets[v] = [n] : buckets[v].push(n);

const res = [];
for (let i = buckets.length - 1; i > -1; i--) {
if (res.length >= k) break;
if (buckets[i]) res.push(...buckets[i]);
}

return res.length > k ? res.slice(0, k) : res;
};

7、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

· 阅读需 3 分钟

1、题干

给定一个整数数组 nums 和一个整数 k ,请返回其中出现频率前 k 高的元素。可以按 任意顺序 返回答案。

 

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:

输入: nums = [1], k = 1
输出: [1]

 

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

 

进阶:所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

 

注意:本题与主站 347 题相同:https://leetcode-cn.com/problems/top-k-frequent-elements/

2、解法1-API排序

先使用哈希映射 map 对所有整数进行计数,然后将 map 中的键值对存入数组并使用排序API按数量降序排序,最后返回前 k 个数字


3、代码

var topKFrequent = function (nums, k) {
const map = new Map();
for (const n of nums) map.set(n, 1 + (map.get(n) || 0));

const arr = [];
for (const kv of map) arr.push(kv);
return arr.sort((a, b) => b[1] - a[1]).slice(0, k).map(a => a[0]);
};

4、复杂度

  • 时间复杂度:O(nlogn)O(nlogn)
  • 空间复杂度:O(n)O(n)

5、解法2-桶排序

先使用哈希映射 map 对所有整数进行计数,然后将 map 中的键值对存入按数量存入桶中,最后返回前 k 个数字。其时间复杂度为 O(n)O(n),解决了进阶问题。


6、代码

var topKFrequent = function (nums, k) {
const map = new Map();
for (const n of nums) map.set(n, 1 + (map.get(n) || 0));

const buckets = new Array(nums.length + 1);
for (const [n, v] of map) !buckets[v] ? buckets[v] = [n] : buckets[v].push(n);

const res = [];
for (let i = buckets.length - 1; i > -1; i--) {
if (res.length >= k) break;
if (buckets[i]) res.push(...buckets[i]);
}

return res.length > k ? res.slice(0, k) : res;
};

7、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)