215.數組中的第k個最大元素(JS)

數組中的第K個最大元素

Category Difficulty Likes Dislikes
algorithms Medium (57.96%) 225 -
Tags

divide-and-conquer | heapjavascript

Companies
在未排序的數組中找到第 **k** 個最大的元素。請注意,你須要找的是數組排序後的第 k 個最大的元素,而不是第 k 個不一樣的元素。

示例 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) {
    
};
複製代碼

1 全排序

這應該是最容易想到的方式了app

var findKthLargest = function (nums, k) {
  nums = nums.sort((a, b) => b - a)
  return nums[k - 1]
};
複製代碼

時間複雜度:nlognless

空間複雜度:O(1)ide

可是很明顯,這種作法很很差,好比數組長度爲1w,咱們只要取第3個,這就很尷尬了函數

2部分排序(冒泡)

因此,咱們就思考着,直接給前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]
};
複製代碼

那時間複雜度就是O(n*k)

3部分排序(堆)

這個部分排序還能夠再精簡一點,好比咱們要的是第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()
};
複製代碼

這樣時間複雜度就能夠降到O(nlogk),可是使用堆結構就會額外的時候必定的空間,空間複雜度就是O(k)

固然這裏的堆結構要本身實現

若是不知道怎麼寫的能夠看我寫的堆及堆排序(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]
  }
}
複製代碼

4 快速選擇

其實在面對一個找出某一符合元素的題,即搜索題時

咱們常常會立馬想到二分查找

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數組中,而不在rightleft之間

因此咱們須要對上面這種寫法的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
}
複製代碼

複雜度分析

  • 時間複雜度 : 平均狀況 O(N),最壞狀況 O(N^2)
  • 空間複雜度 : O(1)
相關文章
相關標籤/搜索