この解答例はChatGPTによって生成されています,正しいかは自己判断で。

BFE.devでのAI解答例 - JavaScript コーディング問題
45. ソートしてない配列にてK番目に大きい要素を見つける

解法1: Quick Select

Quick Selectは、Quick Sortの応用であり、特定の要素をソートする必要がない場合に最適です。これは、一定の時間内にK番目大きい数字を見つけることができることが知られています。

アルゴリズムのアイデアは以下の通りです。

  • 配列の中からピボットを選んで、それより大きい数字を右に、小さい数字を左に分割します。
  • 分割後、ピボットがソートされた配列の何番目に位置するかを調べます。
  • もし、ピボットがK番目の数字に位置しているならそれを返します。
  • そうでなければ、右側にあるか左側にあるかを判断して再帰的に行います。

以下が実装例です。

function quickSelect(arr, k) {
  const pivotIndex = Math.floor(Math.random() * arr.length);
  const pivot = arr[pivotIndex];
  const left = [];
  const right = [];

  for (let i = 0; i < arr.length; i++) {
    if (i !== pivotIndex) {
      arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);
    }
  }

  if (right.length >= k) {
    return quickSelect(right, k);
  } else if (right.length === k - 1) {
    return pivot;
  } else {
    return quickSelect(left, k - right.length - 1);
  }
}

このアルゴリズムでは、ピボットをランダムに選ぶことで最悪の場合でもO(n)の時間でK番目の数字を見つけることができます。

解法2: Heap

Heapを使ってもK番目の数字を見つけることができます。Heapは、最大/最小の要素をO(1)で見つけることができるという特徴がありますが、他の要素を見つけるのには線形時間がかかるため、この問題には不向きです。

しかし、K番目の数字を見つけるためにはK個の最大/最小の数字に興味がありますので、Kサイズの最小Heap(最大Heap)を使って部分的に配列をソートすることができます。

以下が実装例です。

function findKthLargest(arr, k) {
  const heap = new MinHeap();

  for (let i = 0; i < arr.length; i++) {
    heap.insert(arr[i]);
    if (heap.size() > k) {
      heap.extract();
    }
  }

  return heap.extract();
}

class MinHeap {
  constructor() {
    this.heap = [null];
  }

  size() {
    return this.heap.length - 1;
  }

  insert(value) {
    this.heap.push(value);
    this.bubbleUp(this.size());
  }

  extract() {
    if (this.size() < 1) {
      return null;
    }

    const value = this.heap[1];
    const lastValue = this.heap.pop();
    if (this.size() > 0) {
      this.heap[1] = lastValue;
      this.bubbleDown(1);
    }
    return value;
  }

  bubbleUp(index) {
    const value = this.heap[index];

    while (index > 1 && value < this.heap[Math.floor(index / 2)]) {
      this.heap[index] = this.heap[Math.floor(index / 2)];
      index = Math.floor(index / 2);
    }

    this.heap[index] = value;
  }

  bubbleDown(index) {
    const value = this.heap[index];

    while (2 * index <= this.size()) {
      let childIndex = 2 * index;
      if (childIndex + 1 <= this.size() && this.heap[childIndex + 1] < this.heap[childIndex]) {
        childIndex++;
      }
      if (value <= this.heap[childIndex]) {
        break;
      }

      this.heap[index] = this.heap[childIndex];
      index = childIndex;
    }

    this.heap[index] = value;
  }
}

このアルゴリズムは、配列の長さがNのとき、最初にK個の数字をHeapに挿入するためにO(KlogK)の時間がかかります。その後、残りの(N - K)個の数字を一つずつ取り出してHeapに挿入します。Heapの最小値には常にK番目大きい数字が含まれるため、配列からの取り出しは最大でもN - K回行います。O(logK)時間で最小値を取り出せるため、結局のところこのアルゴリズムの計算量はO(KlogK + (N - K)logK) = O(NlogK)となります。