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
}
複製代碼
複雜度分析