连续中值
这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。
正文
面试题 17.20. 连续中值
随机产生数字并传递给一个方法。你能否完成这个方法,在每次产生新值时,寻找当前所有值的中间值(中位数)并保存。
中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
解析
这是一道leetcode 困难难度的算法题,但是个人觉得并没有那么困难。设计数据结构和方法考察了你对问题解决的思路和设计。
设计思路
求中位数,目标是求最中间的数,那么就少不了存储数据和排序。涉及到多个数据的存储以及排序,那么设计的第一想法肯定是数组。所以实现构造函数就是设定一个存储数据的数组。
addNum
方法就是将数据添加到数组中,值得注意的是,这里并不会对所有的数组进行排序,而是将插入的数据,按照大小插入到指定位置中即可,如果每一个数据都是按照顺序进行插入的,那么这个数组一定就是排序完成的数组。
findMedian
方法就是找到中位数,一个已经排好序的数组想要找到中位数的解法的简单程度不言而喻。
实现构造函数
var MedianFinder = function() {
this.dataList = [] // 数值数组
};
这里只需要设置一个接收所有数据的数组
实现 addNum
/**
* @param {number} num
* @return {void}
*/
MedianFinder.prototype.addNum = function(num) {
// 插入
if (this.dataList.length === 0) {
this.dataList.push(num)
} else {
let insertIndex = this.dataList.length
for (let index = 0 ; index < this.dataList.length; index++) {
if (this.dataList[index] > num) {
insertIndex = index // 插入位置
break
}
}
this.dataList.splice(insertIndex, 0 , num)
}
};
利用插入的原理实现排序。
实现 findMedian
* @return {number}
*/
MedianFinder.prototype.findMedian = function() {
if (this.dataList.length % 2 === 0) {
// 偶数
const index = this.dataList.length / 2 - 1
return (this.dataList[index] + this.dataList[index + 1]) / 2
} else {
return this.dataList[parseInt(this.dataList.length / 2)]
}
};
分别对数组个数为偶数和奇数做出不同的处理。
完整代码:
/**
* initialize your data structure here.
*/
var MedianFinder = function () {
this.dataList = [] // 数值数组
};
/**
* @param {number} num
* @return {void}
*/
MedianFinder.prototype.addNum = function (num) {
// 插入
if (this.dataList.length === 0) {
this.dataList.push(num)
} else {
let insertIndex = this.dataList.length
for (let index = 0; index < this.dataList.length; index++) {
if (this.dataList[index] > num) {
insertIndex = index // 插入位置
break
}
}
this.dataList.splice(insertIndex, 0, num)
}
};
/**
* @return {number}
*/
MedianFinder.prototype.findMedian = function () {
if (this.dataList.length % 2 === 0) {
// 偶数
const index = this.dataList.length / 2 - 1
return (this.dataList[index] + this.dataList[index + 1]) / 2
} else {
return this.dataList[parseInt(this.dataList.length / 2)]
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* var obj = new MedianFinder()
* obj.addNum(num)
* var param_2 = obj.findMedian()
*/
提交!
使用链表
上述算法使用到了在数组中插入的操作,通常在数组中插入数据的操作会比较繁琐,原因是既要找到插入位置,又要讲所有插入位置后的数据往后移动一位。在 JS中我们是可以通过 splice
方法去解决,如果在通用的算法中,这里我们就比较合适的应用就是链表。
1. 创建链表数据结构
在JS 中是没有链表这种数据结构的,所以需要我们自己写一个构造函数
var LinkList = function (val, next) {
this.val = val
this.next = next || null
}
2. 实现构造函数
var MedianFinder = function() {
this.linkList = new LinkList(Number.MIN_VALUE, null) // 创建一个链表,并且创建一个虚拟节点
this.length = 0
};
这里的构造函数中,存放数据的是链表结构,第一个节点我们并不知道是什么数据,所以使用最小值作为虚拟节点。 由于链表要想知道长度就必须遍历,在操作过程中为了知道长度去遍历是比较消耗内存的,所以我们可以单独设置一个长度字段,用来记录长度。
3.实现 addNum
/**
* @param {number} num
* @return {void}
*/
MedianFinder.prototype.addNum = function(num) {
// 遍历链表 插入数值
let isAdded = false
let p = this.linkList
while(p.next && !isAdded) {
const next = p.next
if (next.val >= num) {
p.next = new LinkList(num, next)
isAdded = true
p = p.next
}
p = p.next
}
if (!isAdded) {
p.next = new LinkList(num, null)
}
this.length++
};
链表的优势在这里体现,插入数值比数组性能上要优一些。每插入一次数据,记得 length++
3.实现 findMedian
/**
* @return {number}
*/
MedianFinder.prototype.findMedian = function() {
let index = 0
let p = this.linkList.next
while(index < this.length / 2 -1) {
index++
p = p.next
}
return this.length % 2 === 0 ? (p.val + p.next.val) / 2 : p.val
};
这里的原理和数组一致,遍历取中值。
4. 完整代码:
/**
* initialize your data structure here.
*/
var LinkList = function (val, next) {
this.val = val
this.next = next || null
}
var MedianFinder = function() {
this.linkList = new LinkList(Number.MIN_VALUE, null) // 创建一个链表,并且创建一个虚拟节点
this.length = 0
};
/**
* @param {number} num
* @return {void}
*/
MedianFinder.prototype.addNum = function(num) {
// 遍历链表 插入数值
let isAdded = false
let p = this.linkList
while(p.next && !isAdded) {
const next = p.next
if (next.val >= num) {
p.next = new LinkList(num, next)
isAdded = true
p = p.next
}
p = p.next
}
if (!isAdded) {
p.next = new LinkList(num, null)
}
this.length++
};
/**
* @return {number}
*/
MedianFinder.prototype.findMedian = function() {
let index = 0
let p = this.linkList.next
while(index < this.length / 2 -1) {
index++
p = p.next
}
return this.length % 2 === 0 ? (p.val + p.next.val) / 2 : p.val
};
/**
* Your MedianFinder object will be instantiated and called as such:
* var obj = new MedianFinder()
* obj.addNum(num)
* var param_2 = obj.findMedian()
*/