[Algorithm] 7.Top ‘K’ Elements

Darcy Daeseok YU ·2025년 2월 27일

7.Top ‘K’ Elements With Heap

Why using heap?

To find largest or smallest element or stream of data using heaps or sorting.

find kth element => heap[0] element is largest or smallest
heap.pop() k time would find kth largest or smallest element.

[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
-> [9, 6, 4, 5, 5,3, 2, 1, 1, 3,3] //_**not like sorted **_

         9
      /    \
     6      4
    / \    / \
   5   5  3   2
  / \    /
 1   1  3
 /
5

*heap?
heap

Finding a index of Parent or Child node

  1. parent to child
    left child index : parentIdx x 2 + 1
    right child index : parentIdx x 2 + 2
  1. child to parent
    parent index : Math.floor((childIndex - 1) / 2)

Time Complexity of JavaScript sort():

Best Case (already sorted array):

Timsort: O(n)
QuickSort (if used): O(n log n)
Average Case (random array):

Timsort: O(n log n)
QuickSort (if used): O(n log n)
Worst Case (reverse sorted or difficult cases):

Timsort: O(n log n)
QuickSort (if used): O(n^2)

1. Kth Largest Element in an Array (LeetCode #215)

Using min-heap

function findKthLargest(nums: number[], k: number): number {
    const heap = nums.slice(0, k);

    // min-heap
    function heapify (_heap: number[], idx: number) {
        let minIdx = idx; 
        const left = 2 * idx + 1; 
        const right = 2 * idx + 2; 
        if(left < _heap.length && _heap[left] < _heap[minIdx]){
            minIdx = left; 
        }
        if(right < _heap.length && _heap[right] < _heap[minIdx]){
            minIdx = right;
        }
        if(minIdx !== idx){
            [_heap[idx] , _heap[minIdx]] = [_heap[minIdx], _heap[idx]] 
            heapify(_heap, minIdx)
        }
    }

    // buildMinHeap(heap) 
    for(let i = Math.floor(heap.length / 2) ; i>= 0 ;  i-- ){
        heapify(heap, i);
    }

    //go through rest of the items
    for (let i=k; i< nums.length; i++) {
        if (nums[i] > heap[0])
        {
            heap[0] = nums[i];
            heapify(heap, 0)
        }
    }

    return heap[0]
};

Using max-heap

function kthLargestEl() {
  let nums = [3, 2, 3, 1, 2, 4, 5, 5, 6],
    k = 4;
    
  const heap: number[] = [];

  function heapify(i: number, before?: number) {
    console.log("parent ====  ", i, before);
    // findLargestNumIndex
    const leftIdx = i * 2 + 1;
    const rightIdx = i * 2 + 2;
    let largestIdx = i;

    if (leftIdx < heap.length && heap[leftIdx] > heap[largestIdx]) {
      largestIdx = leftIdx;
    }
    if (rightIdx < heap.length && heap[rightIdx] > heap[largestIdx]) {
      largestIdx = rightIdx;
    }

    // swap
    if (largestIdx !== i) {
      [heap[largestIdx], heap[i]] = [heap[i], heap[largestIdx]];
      heapify(largestIdx, i);
    }
  }

  // buildHeap : O(k) better than O(n) with ...nums entire element.
  heap.push(...nums.slice(0,k)); 

  for (let i = Math.floor(heap.length / 2); i >= 0; i--) {
    heapify(i);
  }

  console.log(heap);
  let result = 0;
  for (let i = 0; i < k; i++) {
    result = heap.pop()!;
  }

  console.log(heap, result);
}

Using Map

function findKthLargest(nums: number[], k: number): number {
  const map = new Map();

  for (const num of nums) {
    map.set(num, (map.get(num) || 0) + 1);
  }

  const minNum = Math.min(...nums);
  const maxNum = Math.max(...nums);

  let count = 0;

  for (let i = maxNum; i >= minNum; i--) {
    if (map.has(i)) {
      count += map.get(i);
    }

    if (count >= k) {
      return i;
    }
  }

  return -1;
}

QuickSelect Algorithm
duplicate values 많은 경우 느림 TLE발생(Time Limit Exceeded)

export function findKthLargest(nums: number[], k: number): number {
  const targetIndex = nums.length - k;

  function partition(left: number, right: number): number {
    const pivot = nums[right];
    let i = left;

    for (let j = left; j < right; j++) {
      if (nums[j] <= pivot) {
        // swap elements
        [nums[i], nums[j]] = [nums[j], nums[i]];
        i++;
      }
    }
    [nums[i], nums[right]] = [nums[right], nums[i]];
    return i;
  }

  function quickSelect(left: number, right: number): number {
    const pivotIndex = partition(left, right);
    if (pivotIndex === targetIndex) {
      return nums[pivotIndex];
    } else if (pivotIndex < targetIndex) {
      return quickSelect(pivotIndex + 1, right);
    } else {
      return quickSelect(left, pivotIndex - 1);
    }
  }

  return quickSelect(0, nums.length - 1);
}

QuickSelect Algorithm

function findKthLargest(nums: number[], k: number): number {
  const targetIndex = nums.length - k;

  function partition(left: number, right: number): [number, number] {
    const randomIndex = left + Math.floor(Math.random() * (right - left + 1));

    [nums[randomIndex], nums[right]] = [nums[right], nums[randomIndex]];

    const pivot = nums[right];
    let i = left,
      j = left,
      n = right;

    while (j <= n) {
      if (nums[j] < pivot) {
        [nums[i], nums[j]] = [nums[j], nums[i]];
        i++;
        j++;
      } else if (nums[j] > pivot) {
        [nums[j], nums[right]] = [nums[right], nums[j]];
        n--;
      } else {
        j++;
      }
    }

    return [i, n];
  }

  function quickSelect(left: number, right: number): number {
    const [low, high] = partition(left, right);
    if (targetIndex >= low && targetIndex <= high) {
      return nums[targetIndex];
    } else if (targetIndex < low) {
      return quickSelect(left, low - 1);
    } else {
      return quickSelect(high + 1, right);
    }
  }

  return quickSelect(0, nums.length - 1);
}

Using MaxHeap Class

function findKthLargest4WithClass(nums: number[], k: number): number {
  let heap = new MaxHeap(nums);
  let result: number = 0;
  for (let i = 0; i < k; i++) {
    result = heap.pop();
  }

  return result;
}

class MaxHeap {
  heap: number[] = [];

  constructor(nums: number[]) {
    this.heap = [];
    this.buildHeap(nums);
  }

  leftChildIndx(parent: number) {
    return parent * 2 + 1;
  }
  rightChildIndex(parent: number) {
    return parent * 2 + 2;
  }

  swap(a: number, b: number) {
    [this.heap[a], this.heap[b]] = [this.heap[b], this.heap[a]];
  }

  findLargestIndex(parent: number) {
    const left = this.leftChildIndx(parent);
    const right = this.rightChildIndex(parent);

    let largest = parent;
    if (left < this.heap.length && this.heap[left] > this.heap[largest]) {
      largest = left;
    }

    if (right < this.heap.length && this.heap[right] > this.heap[largest]) {
      largest = right;
    }

    return largest;
  }

  filterDown() {
    let index = 0;
    while (index < this.heap.length) {
      let largest = this.findLargestIndex(index);
      if (largest !== index) {
        this.swap(index, largest);
        index = largest;
      } else {
        break;
      }
    }
  }

  pop() {
    let result = this.heap[0];
    this.heap[0] = this.heap.pop()!;
    this.filterDown();

    return result;
  }

  buildHeap(arr: number[]) {
    this.heap.push(...arr);

    const n = arr.length;

    const heapify = (index: number) => {
      let largest = this.findLargestIndex(index);

      if (largest !== index) {
        this.swap(largest, index);
        heapify(largest);
      }
    };

    for (let i = Math.floor(n / 2); i >= 0; i--) {
      heapify(i);
    }
  }
}

2. Top K Frequent Elements (LeetCode #347)

Using heap


// heap
export function topKFrequentHeap(nums: number[], k: number): number[] {
  const freqMap = new Map<number, number>();

  for (const num of nums) {
    freqMap.set(num, (freqMap.get(num) || 0) + 1);
  }

  const maxHeap: HeapItem[] = Array.from(freqMap.entries()).map(
    ([num, freqCnt]) => ({ num, freqCnt })
  );

  function findBiggerChildIdx(parentIdx: number) {
    let largest = parentIdx;
    const left = parentIdx * 2 + 1;
    const right = parentIdx * 2 + 2;

    if (
      left < maxHeap.length &&
      maxHeap[left].freqCnt > maxHeap[largest].freqCnt
    ) {
      largest = left;
    }

    if (
      right < maxHeap.length &&
      maxHeap[right].freqCnt > maxHeap[largest].freqCnt
    ) {
      largest = right;
    }

    return largest;
  }

  function heapify(parentIdx: number) {
    const largestChildIdx = findBiggerChildIdx(parentIdx);

    if (largestChildIdx !== parentIdx) {
      [maxHeap[parentIdx], maxHeap[largestChildIdx]] = [
        maxHeap[largestChildIdx],
        maxHeap[parentIdx],
      ];

      heapify(largestChildIdx);
    }
  }

  function pop() {
    const largest = maxHeap[0];

    if (maxHeap.length > 1) {
      maxHeap[0] = maxHeap.pop()!;

      let index = 0;
      while (index < maxHeap.length) {
        const largestChildIdx = findBiggerChildIdx(index);
        if (index !== largestChildIdx) {
          [maxHeap[index], maxHeap[largestChildIdx]] = [
            maxHeap[largestChildIdx],
            maxHeap[index],
          ];

          index = largestChildIdx;
        } else {
          break;
        }
      }
    }

    return largest.num;
  }

  // build heap
  for (let i = Math.floor(maxHeap.length / 2) - 1; i >= 0; i--) {
    heapify(i);
  }

  const result = [];
  for (let i = 0; i < k; i++) {
    result.push(pop());
  }

  return result;
}

Using map and sort

function topKFrequent(nums: number[], k: number): number[] {
  const freq = new Map<number, number>();

  for (const num of nums) {
    freq.set(num, (freq.get(num) || 0) + 1);
  }

  return Array.from(freq)
    .sort((a, b) => b[1] - a[1])
    .slice(0, k)
    .map((val) => Number(val[0]));
}


3. Find K Pairs with Smallest Sums (LeetCode #373)

Using min-heap


// (a,b) => a[0] - b[0]
type CompareFn<T> = (a: T, b: T) => number;

class BinaryMinHeap<T> {
  heap: T[];

  compare: CompareFn<T>;

  constructor(compareFn: CompareFn<T>) {
    this.heap = [];
    this.compare = compareFn;
  }

  pop(): T | undefined {
    if (this.heap.length < 2) {
      return this.heap.pop();
    }

    const samllest = this.heap[0];

    this.heap[0] = this.heap.pop()!;

    this.sinkDown(this.heap, this.compare, 0);

    return samllest;
  }

  push(value: T): number {
    const length = this.heap.push(value);
    this.bubbleUp(this.heap, this.compare, length - 1);
    return length;
  }

  bubbleUp<T>(arr: Array<T>, compareFn: CompareFn<T>, index: number) {
    const value = arr[index];

    while (index > 0) {
      // Math.floor((index - 1) / 2);
      const parentIndex = (index - 1) >> 1;
      if (compareFn(arr[parentIndex], value) <= 0) {
        break;
      }

      arr[index] = arr[parentIndex];
      index = parentIndex;
    }

    arr[index] = value;
  }

  // sinkDown<T>(arr: Array<T>, compareFn: CompareFn<T>, parentIdx: number) {
  //   const N = arr.length;

  //   // Find the indices of the two children (left and right) of the parent
  //   const leftChildIdx = 2 * parentIdx + 1;
  //   const rightChildIdx = 2 * parentIdx + 2;

  //   // Find the index of the smallest (or largest in case of a max-heap) child
  //   let smallestChildIdx = parentIdx;

  //   // Check if the left child exists and is smaller than the current element
  //   if (
  //     leftChildIdx < N &&
  //     compareFn(arr[leftChildIdx], arr[smallestChildIdx]) < 0
  //   ) {
  //     smallestChildIdx = leftChildIdx;
  //   }

  //   // Check if the right child exists and is smaller than the current element
  //   if (
  //     rightChildIdx < N &&
  //     compareFn(arr[rightChildIdx], arr[smallestChildIdx]) < 0
  //   ) {
  //     smallestChildIdx = rightChildIdx;
  //   }

  //   // If the smallest child is different from the parent, swap and recursively heapify
  //   if (smallestChildIdx !== parentIdx) {
  //     [arr[parentIdx], arr[smallestChildIdx]] = [
  //       arr[smallestChildIdx],
  //       arr[parentIdx],
  //     ];

  //     // Recursively heapify the affected subtree
  //     this.sinkDown(arr, compareFn, smallestChildIdx);
  //   }
  // }

  
  sinkDown<T>(arr: Array<T>, compareFn: CompareFn<T>, index: number) {
    const value = arr[index];
    const N = arr.length;
    const mid = Math.floor(arr.length / 2) - 1;
    // const mid = (N - 1) / 2;

    while (index <= mid) {
      let childIndex = (index << 1) + 1;

      // +(true) = 1 , +(false) = 0
      childIndex += +(
        childIndex + 1 < N &&
        compareFn(arr[childIndex + 1], arr[childIndex]) <= 0
      );

      if (compareFn(value, arr[childIndex]) <= 0) {
        break;
      }

      arr[index] = arr[childIndex];
      index = childIndex;
    }
    arr[index] = value;
  }
}

type HeapItem = [sum: number, nums1Value: number, nums2Index: number];
function kSmallestPairs(
  nums1: number[],
  nums2: number[],
  k: number
): number[][] {
  const result: number[][] = [];
  if (nums1.length === 0 || nums2.length === 0 || k === 0) return result;

  const minHeap = new BinaryMinHeap<HeapItem>((a, b) => a[0] - b[0]);

  const N = Math.min(nums1.length, k);
  const M = Math.min(nums2.length, k);

  for (let i = 0; i < N; i++) {
    minHeap.push([nums1[i] + nums2[0], nums1[i], 0]);
  }

  while (k-- > 0 && minHeap.heap.length > 0) {
    const [sum, nums1Value, nums2Index] = minHeap.pop()!;

    result.push([nums1Value, sum - nums1Value]);

    if (nums2Index + 1 < M) {
      minHeap.push([
        nums1Value + nums2[nums2Index + 1],
        nums1Value,
        nums2Index + 1,
      ]);
    }
  }

  return result;
}

Using min-heap2


function kSmallestPairs(
  nums1: number[],
  nums2: number[],
  k: number
): number[][] {
  const result: number[][] = Array.from({ length: k }, () => []);

  const length = result.length;

  const minHeap = new MinHeap();

  for (let i = 0; i < Math.min(nums1.length, k); ++i) {
    minHeap.insert({
      sum: nums1[i] + nums2[0],
      num1: nums1[i],
      num2: nums2[0],
      index2: 0,
    });
  }

  while (k > 0 && !minHeap.isEmpty()) {
    const { num1, num2, index2 } = minHeap.pop();
    result[length - k] = [num1, num2];

    const nextIndex2 = index2 + 1;

    if (nextIndex2 < nums2.length) {
      minHeap.insert({
        sum: num1 + nums2[nextIndex2],
        num1,
        num2: nums2[nextIndex2],
        index2: nextIndex2,
      });
    }

    --k;
  }

  return result;
}


interface INode {
  sum: number;
  num1: number;
  num2: number;
  index2: number;
}

class MinHeap {
  private nodes: INode[] = [];

  constructor() {
    this.nodes = [];
  }

  private swap(a: number, b: number) {
    [this.nodes[a], this.nodes[b]] = [this.nodes[b], this.nodes[a]];
  }

  private shouldSwap(parentIdx: number, childIdx: number) {
    if (parentIdx < 0 || parentIdx >= this.size()) {
      return false;
    }
    if (childIdx < 0 || childIdx >= this.size()) {
      return false;
    }

    return this.nodes[parentIdx].sum > this.nodes[childIdx].sum;
  }

  private heapifyUp(startIdx: number) {
    let childIndex = startIdx;
    let parentIndex = Math.floor((childIndex - 1) / 2);

    while (this.shouldSwap(parentIndex, childIndex)) {
      this.swap(parentIndex, childIndex);
      childIndex = parentIndex;

      parentIndex = Math.floor((childIndex - 1) / 2);
    }
  }

  private heapifyDown(startIdx: number) {
    const length = this.size();
    const leftChildIndex = 2 * startIdx + 1;
    const rightChildIndex = 2 * startIdx + 2;

    let samllest = startIdx;

    if (
      leftChildIndex < length &&
      this.nodes[leftChildIndex].sum < this.nodes[samllest].sum
    ) {
      samllest = leftChildIndex;
    }

    if (
      rightChildIndex < length &&
      this.nodes[rightChildIndex].sum < this.nodes[samllest].sum
    ) {
      samllest = rightChildIndex;
    }

    if (samllest !== startIdx) {
      this.swap(startIdx, samllest);
      this.heapifyDown(samllest);
    }
  }

  size() {
    return this.nodes.length;
  }

  isEmpty() {
    return this.size() === 0;
  }

  insert(value: INode) {
    this.nodes.push(value);
    this.heapifyUp(this.size() - 1);
  }

  pop() {
    if (this.size() === 1) {
      return this.nodes.pop()!;
    }

    const minValue = this.nodes[0];
    this.nodes[0] = this.nodes.pop()!;

    this.heapifyDown(0);

    return minValue;
  }
}
profile
React, React-Native https://darcyu83.netlify.app/

0개의 댓글