Category | Difficulty | Likes | Dislikes |
---|---|---|---|
algorithms | Medium (57.96%) | 225 | - |
divide-and-conquer
| heap
javascript
示例 1:java
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
复制代码
示例 2:web
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
复制代码
说明:数组
你能够假设 k 老是有效的,且 1 ≤ k ≤ 数组的长度。bash
/* * @lc app=leetcode.cn id=215 lang=javascript * * [215] 数组中的第K个最大元素 */
/** * @param {number[]} nums * @param {number} k * @return {number} */
var findKthLargest = function(nums, k) {
};
复制代码
这应该是最容易想到的方式了app
var findKthLargest = function (nums, k) {
nums = nums.sort((a, b) => b - a)
return nums[k - 1]
};
复制代码
时间复杂度:less
空间复杂度:ide
可是很明显,这种作法很很差,好比数组长度为1w,咱们只要取第3个,这就很尴尬了函数
因此,咱们就思考着,直接给前k个数排序就能够了post
var findKthLargest = function (nums, k) {
for (let i = 0; i < k; i++) {
for (let j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1])
[nums[j], nums[j + 1]] = [nums[j + 1], nums[j]]
}
}
return nums[nums.length - k]
};
复制代码
那时间复杂度就是
这个部分排序还能够再精简一点,好比咱们要的是第k个元素,那前k个元素也能够不用排序的嘛
这个时候,不少人就会思考呀?若是不知道把前k个数都排出来,咱们怎么知道知道第k个是谁呢?
那就要引入堆的概念了,若是咱们建立一个只有k 个元素的最小堆,那堆订必定是比堆里的任意元素是小的
var findKthLargest = function (nums, k) {
let minHeap = new MinHeap()
for (let i = 0; i < nums.length; i++) {
if (minHeap.size() < k) minHeap.push(nums[i])
else if (minHeap.top() < nums[i]) {
minHeap.pop()
minHeap.push(nums[i])
}
}
return minHeap.top()
};
复制代码
这样时间复杂度就能够降到,可是使用堆结构就会额外的时候必定的空间,空间复杂度就是
固然这里的堆结构要本身实现
若是不知道怎么写的能够看我写的堆及堆排序(JS)
class MinHeap {
constructor() {
this.heap = []
this.len = 0
}
size() {
return this.len
}
push(val) {
this.heap[++this.len] = val
this.swin(this.len)
}
pop() {
const ret = this.heap[1]
this.swap(1, this.len--)
this.heap[this.len + 1] = null
this.sink(1)
return ret
}
swin(ind) {
while (ind > 1 && this.less(ind, parseInt(ind / 2))) {
this.swap(ind, parseInt(ind / 2))
ind = parseInt(ind / 2)
}
}
sink(ind) {
while (ind * 2 <= this.len) {
let j = ind * 2
if (j < this.len && this.less(j + 1, j)) j++
if (this.less(ind, j)) break
this.swap(ind, j)
ind = j
}
}
top() {
return this.heap[1]
}
isEmpty() {
return this.len === 0
}
swap(i, j) {
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
}
less(i, j) {
return this.heap[i] < this.heap[j]
}
}
复制代码
其实在面对一个找出某一符合元素的题,即搜索题时
咱们常常会立马想到二分查找
function binarySearch(arr, val) {
let low = 0
let high = arr.length - 1
while (low <= high) {
// mid 表示中间值
let mid = Math.floor(low + (high - low) / 2)
if (arr[mid] === val) return mid
val < arr[mid] ? high = mid - 1 : low = mid + 1
}
return null
}
let arr = [0, 1, 2, 3, 4, 5]
console.log(binarySearch(arr, 0));
复制代码
二分查找有一个前提,就是arr[mid]
要是一个中间值,这样才能够排除另外一半
提到中间值,不得不让咱们想到快速排序的pivot
那快排的partition
函数就恰好与二分查找完美的结合
function quickSort(arr) {
return quick(arr, 0, arr.length - 1)
}
function quick(arr, left, right) {
if (arr.length > 1) {
let index = partition(arr, left, right)
if (left < index - 1) quick(arr, left, index - 1)
if (index < right) quick(arr, index, right)
}
return arr
}
function partition(arr, left, right) {
const pivot = arr[Math.floor(left + (right - left) / 2)]
let i = left
let j = right
while (i <= j) {
while (arr[i] < pivot) i++
while (pivot < arr[j]) j--
if (i <= j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++; j--;
}
}
return i
}
let arr = [11, 2, 33, 4, 5]
console.log(quickSort(arr));
复制代码
不懂快速排序的能够看我写的 排序演化(三):快速
可是上面这种写法pivot
会在left
数组中,而不在right
和left
之间
因此咱们须要对上面这种写法的partition
中修改一下,使得pivot
的值脱离出来
function partition(arr, left, right) {
let mid = Math.floor(left + (right - left) / 2)
const pivot = arr[mid]
// 把pivot放在arr的最后面
[arr[mid], arr[right]] = [arr[right], arr[mid]]
let i = left
// 把pivot排除在外,不对pivot进行排序
let j = right - 1
while (i <= j) {
while (arr[i] < pivot) i++
while (pivot < arr[j]) j--
if (i <= j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++; j--;
}
}
// 由于arr[i]是属于left的,pivot也是属于left的
// 故咱们能够把本来保护起来的pivot和如今数组的中间值交换
[arr[right], arr[i]] = [arr[i], arr[right]]
return i
}
复制代码
那如今咱们只要在二分搜索里面添加上述的partition
就能够实现快速查找
可是还须要主要一点的是,咱们是要升序仍是降序呢?
咱们是要求第k个最大元素
那这个数组的顺序应该是
arr = [5,4,3,2,1]
这样的逆序的
好比上面的数组中,第2大
的数就是4,即arr[1]
那咱们的partition
应该按逆序划分,因此上面的代码还要改为
while (i <= j) {
+ while (arr[i] > pivot) i++
- while (arr[i] < pivot) i++
+ while (pivot < arr[j]) j--
- while (pivot < arr[j]) j--
if (i <= j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++; j--;
}
}
复制代码
partition
返回的下标i
,就表示该arr[i]
是该数组的第k-1
位,由于下标是从0开始的,而语义上的第1位,便是i === 0
while (low <= high) {
const mid = partition(arr.low, high)
if (mid === k - 1) return arr[mid]
}
复制代码
那当mid < k -1
表示 第k位 在arr[mid]
的右边,那咱们只要查找arr[mid+1]~arr[high]
便可
而mid > k - 1
表示 第k位在arr[mid]
的左边,那那么咱们只要查找arr[low] ~ arr[mid - 1]
因此代码就是
while (low <= high) {
const mid = partition(nums, low, high)
if (mid === k - 1) return nums[mid]
mid < k - 1 ? low = mid + 1 : high = mid - 1
}
复制代码
最终的代码就是
/* * @lc app=leetcode.cn id=215 lang=javascript * * [215] 数组中的第K个最大元素 */
/** * @param {number[]} nums * @param {number} k * @return {number} */
var findKthLargest = function (nums, k) {
let low = 0
let high = nums.length - 1
while (low <= high) {
const mid = partition(nums, low, high)
if (mid === k - 1) return nums[mid]
mid < k - 1 ? low = mid + 1 : high = mid - 1
}
}
function partition(arr, low, high) {
let mid = Math.floor(low + (high - low) / 2)
const pivot = arr[mid]; // 这里记得添加分号
// 把pivot放在arr的最后面
[arr[mid], arr[high]] = [arr[high], arr[mid]]
let i = low
// 把pivot排除在外,不对pivot进行排序
let j = high - 1
while (i <= j) {
while (arr[i] > pivot) i++
while (arr[j] < pivot) j--
if (i <= j) {
[arr[i], arr[j]] = [arr[j], arr[i]]
i++; j--;
}
}
// 由于arr[i]是属于left的,pivot也是属于left的
// 故咱们能够把本来保护起来的pivot和如今数组的中间值交换
[arr[high], arr[i]] = [arr[i], arr[high]]
return i
}
复制代码
复杂度分析