AlgoAtlas

Heap

Phase 2 12 solutions

Heap / Priority Queue

mindmap
  root((Heap))
    Top-K
      Kth Largest Element In a Stream LC703
      K Closest Points to Origin LC973
      Kth Largest Element In An Array LC215
    Two Heaps
      Find Median From Data Stream LC295
      IPO LC502
    Greedy + Heap
      Reorganize String LC767
      Longest Happy String LC1405
      Task Scheduler LC621
    Event Simulation
      Single Threaded CPU LC1834
      Car Pooling LC1094
    Design
      Design Twitter LC355

Problem List

# Problem LC Difficulty
1 Kth Largest Element In a Stream 703 🟢 Easy
2 Last Stone Weight 1046 🟢 Easy
3 K Closest Points to Origin 973 🟡 Medium
4 Kth Largest Element In An Array 215 🟡 Medium
5 Task Scheduler 621 🟡 Medium
6 Design Twitter 355 🟡 Medium
7 Single Threaded CPU 1834 🟡 Medium
8 Reorganize String 767 🟡 Medium
9 Longest Happy String 1405 🟡 Medium
10 Car Pooling 1094 🟡 Medium
11 Find Median From Data Stream 295 🔴 Hard
12 IPO 502 🔴 Hard

Approach Diagrams

Min Heap vs Max Heap Decision

flowchart TD
    A[Heap Problem] --> B{What do you need?}
    B -- Kth largest<br/>or top-K smallest --> C["Min Heap of size K<br/>heapq in Python"]
    B -- Kth smallest<br/>or top-K largest --> D["Max Heap of size K<br/>negate values in Python"]
    B -- Always need<br/>smallest next --> E["Min Heap<br/>LC 1834 CPU, 1094 Car Pooling"]
    B -- Always need<br/>largest next --> F["Max Heap<br/>LC 1046 Last Stone, 767 Reorganize"]
    B -- Need both halves --> G["Two Heaps<br/>LC 295 Median"]

Top-K Pattern Flow

flowchart TD
    A[Find Top-K elements] --> B[Use Min Heap of size K]
    B --> C[For each element x]
    C --> D{"heap size < K?"}
    D -- Yes --> E[Push x]
    D -- No --> F{"x > heap top?"}
    F -- Yes --> G[Pop min, push x]
    F -- No --> H[Skip]
    E --> C
    G --> C
    H --> C
    C --> I[Heap contains K largest]
    I --> J[LC 703, 215, 973]

Two Heaps Pattern — Median Finding

flowchart TD
    A[Add number n] --> B{"n <= max_heap top?"}
    B -- Yes --> C["Push to max_heap<br/>lower half"]
    B -- No --> D["Push to min_heap<br/>upper half"]
    C --> E{"Balance heaps<br/>size diff > 1?"}
    D --> E
    E -- max_heap bigger --> F["Move top of max_heap<br/>to min_heap"]
    E -- min_heap bigger --> G["Move top of min_heap<br/>to max_heap"]
    E -- balanced --> H[Done]
    F --> I[Get Median]
    G --> I
    H --> I
    I --> J{Equal sizes?}
    J -- Yes --> K[avg of both tops]
    J -- No --> L[top of larger heap]

Greedy + Heap Pattern

flowchart TD
    A[Greedy + Heap Problems] --> B{Pattern type}
    B -- Always pick most frequent --> C["Max Heap by frequency<br/>LC 767 Reorganize, 1405 Happy String"]
    C --> D["Pop max freq char<br/>append to result<br/>decrement and re-push"]
    B -- Unlock tasks by capital --> E["Two heaps: available vs locked<br/>LC 502 IPO"]
    E --> F["Min heap on cost<br/>Max heap on profit<br/>Unlock affordable, pick best profit"]
    B -- Cooldown scheduling --> G["Max heap + cooldown queue<br/>LC 621 Task Scheduler"]
    G --> H["Pop most frequent<br/>wait cooldown<br/>re-add to heap"]

Event-Driven Simulation with Heap

flowchart TD
    A[Event Simulation] --> B[Sort events by start time]
    B --> C["Min heap tracks active events<br/>by end time or priority"]
    C --> D[Process next event]
    D --> E{"Any events ended<br/>before current start?"}
    E -- Yes --> F["Pop from heap<br/>free resources"]
    E -- No --> G[All slots busy]
    F --> H[Assign to freed slot]
    G --> I["Wait / queue"]
    H --> J[Push new end time to heap]
    I --> J
    J --> D
    D --> K[LC 1834 CPU, 1094 Car Pooling]

Complexity Summary

Problem Time Space Pattern
Kth Largest in Stream (703) O(log k) per add O(k) Min heap size k
Last Stone Weight (1046) O(n log n) O(n) Max heap
K Closest Points (973) O(n log k) O(k) Min heap size k
Kth Largest in Array (215) O(n log k) O(k) Min heap size k
Task Scheduler (621) O(n log n) O(n) Max heap + cooldown
Design Twitter (355) O(k log n) per feed O(n) Min heap merge
Single Threaded CPU (1834) O(n log n) O(n) Min heap by duration
Reorganize String (767) O(n log k) O(k) Max heap by freq
Longest Happy String (1405) O(n log k) O(k) Max heap by freq
Car Pooling (1094) O(n log n) O(n) Min heap by dropoff
Find Median (295) O(log n) add, O(1) query O(n) Two heaps
IPO (502) O(n log n) O(n) Two heaps

Study Order

flowchart LR
    W1["Week 1: Heap Basics<br/>703 → 1046 → 215 → 973"]
    W2["Week 2: Greedy + Heap<br/>767 → 1405 → 621 → 502"]
    W3["Week 3: Simulation<br/>355 → 1834 → 1094"]
    W4["Week 4: Hard<br/>295"]
    W1 --> W2 --> W3 --> W4

Key insight: Python only has heapq (min heap). For max heap, negate values. For top-K largest, maintain a min heap of size K — if new element beats the min, swap it in.

Heap — Pattern Notes

Core Intuition

A heap gives you O(log n) insert and O(1) peek at the min (or max). Use it whenever you need to repeatedly extract the current best from a dynamic set. Python only has heapq (min-heap) — negate values for max-heap.

Sub-patterns:

  • Top-K — maintain a heap of size K; push and pop to keep only K best
  • Two Heaps — max-heap for lower half + min-heap for upper half → O(1) median
  • Greedy + Heap — sort by one dimension, use heap to greedily pick best on another
  • Event Simulation — push events by time/priority, process in order

Decision Diagram

flowchart TD
    A[Heap Problem] --> B{What do you need?}

    B --> C["K largest / smallest elements"]
    B --> D[Running median]
    B --> E["Schedule / assign tasks greedily"]
    B --> F[Simulate events in priority order]

    C --> C1["Min-heap of size K for K largest<br/>Max-heap of size K for K smallest"]
    D --> D1["Two heaps: max-heap left half<br/>min-heap right half<br/>balance sizes on each insert"]
    E --> E1["Sort input, use heap to track<br/>best available option greedily"]
    F --> F1["Push all events; pop by priority<br/>handle dependencies by pushing new events"]

Per-Problem Notes

# Problem LC Difficulty What to Learn Common Mistakes Time / Space
1 Kth Largest in Stream 703 Easy Min-heap of size K; heap[0] is always the Kth largest Heap size > K — must pop when size exceeds K O(log k) add / O(k) space
2 Last Stone Weight 1046 Easy Max-heap (negate); pop two heaviest, push difference if nonzero Forgetting to push back when stones aren't equal O(n log n) / O(n)
3 K Closest Points 973 Medium Min-heap by distance; pop K times. Or max-heap of size K Computing distance with sqrt (unnecessary — compare squared) O(n log k) / O(k)
4 Kth Largest in Array 215 Medium Min-heap of size K; final heap[0] is answer. Or quickselect O(n) avg Using max-heap and popping n-k times (works but slower) O(n log k) / O(k)
5 Task Scheduler 621 Medium Greedy: always schedule most frequent available task. Use max-heap + cooldown queue Not handling idle time correctly; forgetting cooldown queue O(n log n) / O(n)
6 Design Twitter 355 Medium Per-user tweet list; on getNewsFeed merge K lists with max-heap of (timestamp, user, idx) Not limiting to 10 tweets; heap comparison on equal timestamps O(f log f) getNewsFeed / O(n)
7 Single-Threaded CPU 1834 Medium Sort tasks by enqueue time; use min-heap (processing_time, idx); simulate time jumps Not jumping time forward when CPU is idle O(n log n) / O(n)
8 Reorganize String 767 Medium Max-heap by frequency; greedily pick most frequent, then second most frequent Placing same char consecutively; not re-adding char with remaining count O(n log k) / O(k)
9 Longest Happy String 1405 Medium Same greedy as Reorganize String but allow up to 2 consecutive; pick 2nd most freq when top would make 3 Not switching to second most frequent when top would violate constraint O(n log k) / O(k)
10 Car Pooling 1094 Medium Event simulation: push (time, +/-passengers); sort/heap by time; track current passengers Not handling simultaneous drop-off before pick-up at same stop O(n log n) / O(n)
11 Find Median from Stream 295 Hard Two heaps: max-heap (lo) + min-heap (hi); balance so len(lo) == len(hi) or len(lo) == len(hi)+1 Not rebalancing after each insert; median formula for even/odd sizes O(log n) add, O(1) median / O(n)
12 IPO 502 Hard Sort projects by capital; greedily unlock affordable projects into max-heap by profit; pick top K times Not re-checking newly affordable projects after each pick O(n log n) / O(n)

Edge Cases to Watch

  • K larger than array size — return all elements
  • All stones equal weight in Last Stone — result is 0 (even count) or that weight (odd count)
  • Task Scheduler with all unique tasks — no idle time needed
  • Find Median with all same elements — median is that element
  • Car Pooling: drop-off and pick-up at same location — drop off first
  • IPO with 0 initial capital — only projects with capital=0 are initially available
  • Reorganize String impossible — when max_freq > (n+1)//2, return ""
  • Two heaps: always push to lo first, then rebalance to hi if needed
# Problem LC Difficulty
1 Kth Largest Element In a Stream 703 🟢 Easy
2 Last Stone Weight 1046 🟢 Easy
3 K Closest Points to Origin 973 🟡 Medium
4 Kth Largest Element In An Array 215 🟡 Medium
5 Task Scheduler 621 🟡 Medium
6 Design Twitter 355 🟡 Medium
7 Single Threaded CPU 1834 🟡 Medium
8 Reorganize String 767 🟡 Medium
9 Longest Happy String 1405 🟡 Medium
10 Car Pooling 1094 🟡 Medium
11 Find Median From Data Stream 295 🔴 Hard
12 IPO 502 🔴 Hard
Car Pooling â–¼ expand
import heapq
from typing import List


class Solution:
    """
    ## 1094. Car Pooling

    There is a car with `capacity` empty seats. The vehicle only drives east (i.e., it cannot turn
    around and drive west).

    You are given the integer `capacity` and an array `trips` where `trips[i] = [numPassengersi,
    fromi, toi]` indicates that the `i`th trip has `numPassengersi` passengers and the route goes
    from `fromi` to `toi`. The locations are given as the number of kilometers due east from the
    car's initial location.

    Return `true` if it is possible to pick up and drop off all passengers for all the given trips,
    or `false` otherwise.

    ### Examples

    ```text
    Input: trips = [[2,1,5],[3,3,7]], capacity = 4
    Output: false

    Explanation:
        At stop 1: pick up 2 → current = 2
        At stop 3: pick up 3 → current = 5 > 4 → impossible
    ```

    ```text
    Input: trips = [[2,1,5],[3,3,7]], capacity = 5
    Output: true
    ```

    ```text
    Input: trips = [[3,2,7],[3,7,9],[8,3,9]], capacity = 11
    Output: true
    ```

    ### Constraints
    - `1 <= trips.length <= 1000`
    - `trips[i].length == 3`
    - `1 <= numPassengersi <= 100`
    - `0 <= fromi < toi <= 1000`
    - `1 <= capacity <= 10^5`
    """

    def carPooling_events(self, trips: List[List[int]], capacity: int) -> bool:
        # O(n log n) — sort pickup/dropoff events, simulate
        events = []
        for num, start, end in trips:
            events.append((start, num))   # pickup
            events.append((end, -num))    # dropoff
        events.sort()
        current = 0
        for _, delta in events:
            current += delta
            if current > capacity:
                return False
        return True

    def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
        # Sort trips by start; use min-heap of (end, passengers) for active trips
        # At each trip start, drop off passengers from trips that have ended
        # If current passengers exceed capacity, return False
        # O(n + max_stop) — difference array over stop positions
        diff = [0] * 1001
        for num, start, end in trips:
            diff[start] += num
            diff[end] -= num
        current = 0
        for delta in diff:
            current += delta
            if current > capacity:
                return False
        return True


if __name__ == '__main__':
    s = Solution()
    assert s.carPooling_events([[2, 1, 5], [3, 3, 7]], 4) == False
    assert s.carPooling([[2, 1, 5], [3, 3, 7]], 4) == False

    assert s.carPooling_events([[2, 1, 5], [3, 3, 7]], 5) == True
    assert s.carPooling([[2, 1, 5], [3, 3, 7]], 5) == True

    assert s.carPooling([[3, 2, 7], [3, 7, 9], [8, 3, 9]], 11) == True
    assert s.carPooling_events([[3, 2, 7], [3, 7, 9], [8, 3, 9]], 11) == True

    print("All tests passed.")
Design Twitter â–¼ expand
import heapq
from collections import defaultdict
from typing import List


class Twitter:
    """
    ## 355. Design Twitter

    Design a simplified version of Twitter where users can post tweets, follow/unfollow another user,
    and see the 10 most recent tweets in the user's news feed.

    Implement the `Twitter` class:
    - `Twitter()` — initializes the Twitter object.
    - `void postTweet(int userId, int tweetId)` — composes a new tweet with ID `tweetId` by user `userId`.
    - `List<Integer> getNewsFeed(int userId)` — retrieves the 10 most recent tweet IDs in the user's
      news feed. Each item must be posted by users who the user followed or by the user themselves.
      Tweets must be ordered from most recent to least recent.
    - `void follow(int followerId, int followeeId)` — the user with ID `followerId` starts following
      the user with ID `followeeId`.
    - `void unfollow(int followerId, int followeeId)` — the user with ID `followerId` starts unfollowing
      the user with ID `followeeId`.

    ### Examples

    ```text
    Input:
        Twitter twitter = new Twitter();
        twitter.postTweet(1, 5);
        twitter.getNewsFeed(1);   // [5]
        twitter.follow(1, 2);
        twitter.postTweet(2, 6);
        twitter.getNewsFeed(1);   // [6, 5]
        twitter.unfollow(1, 2);
        twitter.getNewsFeed(1);   // [5]
    ```

    ### Constraints
    - `1 <= userId, tweetId <= 500`
    - All the tweets have **unique** IDs.
    - At most `3 * 10^4` calls will be made to `postTweet`, `getNewsFeed`, `follow`, and `unfollow`.
    """

    def __init__(self):
        # O(1) init
        self.timestamp = 0
        self.tweets: dict = defaultdict(list)   # userId -> [(time, tweetId), ...]
        self.following: dict = defaultdict(set)  # userId -> {followeeId}

    def postTweet(self, userId: int, tweetId: int) -> None:
        # Store tweet with a global timestamp for ordering
        # O(1) — append tweet with global timestamp
        self.tweets[userId].append((self.timestamp, tweetId))
        self.timestamp += 1

    def getNewsFeed(self, userId: int) -> List[int]:
        # Collect recent tweets from user and all followees
        # Use heap or sort to get the 10 most recent
        # O(f * 10 * log f) — heap-merge latest tweets from followed users + self
        heap: List = []
        users = self.following[userId] | {userId}
        for uid in users:
            if self.tweets[uid]:
                idx = len(self.tweets[uid]) - 1
                time, tid = self.tweets[uid][idx]
                heapq.heappush(heap, (-time, tid, uid, idx))

        result = []
        while heap and len(result) < 10:
            neg_time, tid, uid, idx = heapq.heappop(heap)
            result.append(tid)
            if idx > 0:
                time, next_tid = self.tweets[uid][idx - 1]
                heapq.heappush(heap, (-time, next_tid, uid, idx - 1))
        return result

    def follow(self, followerId: int, followeeId: int) -> None:
        # Add followee to user's follow set
        # O(1)
        self.following[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        # Remove followee from user's follow set if present
        # O(1)
        self.following[followerId].discard(followeeId)


if __name__ == '__main__':
    t = Twitter()
    t.postTweet(1, 5)
    assert t.getNewsFeed(1) == [5]
    t.follow(1, 2)
    t.postTweet(2, 6)
    assert t.getNewsFeed(1) == [6, 5]
    t.unfollow(1, 2)
    assert t.getNewsFeed(1) == [5]

    t2 = Twitter()
    for i in range(11):
        t2.postTweet(1, i)
    feed = t2.getNewsFeed(1)
    assert len(feed) == 10
    assert feed == list(range(10, 0, -1))

    print("All tests passed.")
Find Median From Data Stream â–¼ expand
import heapq
from typing import List


class MedianFinder:
    """
    ## 295. Find Median from Data Stream

    The **median** is the middle value in an ordered integer list. If the size of the list is even,
    there is no middle value, and the median is the mean of the two middle values.

    Implement the `MedianFinder` class:
    - `MedianFinder()` — initializes the `MedianFinder` object.
    - `void addNum(int num)` — adds the integer `num` from the data stream to the data structure.
    - `double findMedian()` — returns the median of all elements so far. Answers within `10^-5`
      of the actual answer will be accepted.

    ### Examples

    ```text
    Input:
        MedianFinder mf = new MedianFinder();
        mf.addNum(1);
        mf.addNum(2);
        mf.findMedian(); // 1.5
        mf.addNum(3);
        mf.findMedian(); // 2.0
    ```

    ### Constraints
    - `-10^5 <= num <= 10^5`
    - There will be at least one element in the data structure before calling `findMedian`.
    - At most `5 * 10^4` calls will be made to `addNum` and `findMedian`.
    """

    # --- Approach 1: Sort on every findMedian ---

    class _SortApproach:
        def __init__(self):
            self.data: List[int] = []

        def addNum(self, num: int) -> None:
            # Push to max-heap (lower half); rebalance if max of lower > min of upper
            # Keep sizes balanced: small can have at most 1 more element than large
            # O(1) amortized append
            import bisect
            bisect.insort(self.data, num)

        def findMedian(self) -> float:
            # Odd total: median is top of larger heap; even: average of both tops
            # O(1) — data kept sorted via insort
            n = len(self.data)
            return self.data[n // 2] if n % 2 else (self.data[n // 2 - 1] + self.data[n // 2]) / 2.0

    # --- Approach 2: Two heaps ---

    def __init__(self):
        # O(1) init
        self.small: List[int] = []  # max-heap (negated) — lower half
        self.large: List[int] = []  # min-heap — upper half

    def addNum(self, num: int) -> None:
        # Push to max-heap (lower half); rebalance if max of lower > min of upper
        # Keep sizes balanced: small can have at most 1 more element than large
        # O(log n) — push to max-heap, rebalance
        heapq.heappush(self.small, -num)
        # Ensure every element in small <= every element in large
        if self.small and self.large and (-self.small[0]) > self.large[0]:
            heapq.heappush(self.large, -heapq.heappop(self.small))
        # Balance sizes: small can have at most 1 more than large
        if len(self.small) > len(self.large) + 1:
            heapq.heappush(self.large, -heapq.heappop(self.small))
        elif len(self.large) > len(self.small):
            heapq.heappush(self.small, -heapq.heappop(self.large))

    def findMedian(self) -> float:
        # Odd total: median is top of larger heap; even: average of both tops
        # O(1) — peek tops of both heaps
        if len(self.small) > len(self.large):
            return float(-self.small[0])
        return (-self.small[0] + self.large[0]) / 2.0


if __name__ == '__main__':
    mf = MedianFinder()
    mf.addNum(1)
    mf.addNum(2)
    assert mf.findMedian() == 1.5
    mf.addNum(3)
    assert mf.findMedian() == 2.0

    mf2 = MedianFinder()
    mf2.addNum(-1)
    assert mf2.findMedian() == -1.0
    mf2.addNum(-2)
    assert mf2.findMedian() == -1.5
    mf2.addNum(-3)
    assert mf2.findMedian() == -2.0
    mf2.addNum(-4)
    assert mf2.findMedian() == -2.5
    mf2.addNum(-5)
    assert mf2.findMedian() == -3.0

    print("All tests passed.")
Ipo â–¼ expand
import heapq
from typing import List


class Solution:
    """
    ## 502. IPO

    Suppose LeetCode will start its **IPO** soon. In order to sell a good price of its shares to
    Venture Capital, LeetCode would like to work on some projects to increase its capital before
    the IPO. Since it has limited resources, it can only finish at most `k` distinct projects
    before the IPO. Help LeetCode design the best way to maximize its total capital after finishing
    at most `k` distinct projects.

    You are given `n` projects where the `i`th project has a pure profit `profits[i]` and a minimum
    capital of `capital[i]` is needed to start it.

    Initially, you have `w` capital. When you finish a project, you will obtain its pure profit and
    the profit will be added to your total capital.

    Return the **maximized total capital** after finishing at most `k` distinct projects.

    ### Examples

    ```text
    Input: k = 2, w = 0, profits = [1, 2, 3], capital = [0, 1, 1]
    Output: 4

    Explanation:
        Start with w=0. Available: project 0 (capital=0). Pick it → profit=1, w=1.
        Now w=1. Available: projects 1,2 (capital=1). Pick project 2 (profit=3) → w=4.
        Result: 4
    ```

    ```text
    Input: k = 3, w = 0, profits = [1, 2, 3], capital = [0, 1, 2]
    Output: 6
    ```

    ### Constraints
    - `1 <= k <= 10^5`
    - `0 <= w <= 10^9`
    - `n == profits.length == capital.length`
    - `1 <= n <= 10^5`
    - `0 <= profits[i] <= 10^4`
    - `0 <= capital[i] <= 10^9`
    """

    def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
        # Greedily pick the most profitable available project at each step
        # Use min-heap on capital to unlock projects as capital grows
        # Use max-heap on profit to always pick the best available project
        # O(n log n) — sort by capital, greedily pick max profit available project
        projects = sorted(zip(capital, profits))
        available: List[int] = []  # max-heap (negated profits)
        i = 0
        n = len(projects)

        for _ in range(k):
            # Unlock all projects we can now afford
            while i < n and projects[i][0] <= w:
                heapq.heappush(available, -projects[i][1])
                i += 1
            if not available:
                break
            w += -heapq.heappop(available)

        return w


if __name__ == '__main__':
    s = Solution()
    assert s.findMaximizedCapital(2, 0, [1, 2, 3], [0, 1, 1]) == 4
    assert s.findMaximizedCapital(3, 0, [1, 2, 3], [0, 1, 2]) == 6
    assert s.findMaximizedCapital(1, 0, [1, 2, 3], [1, 1, 2]) == 0  # can't afford any
    assert s.findMaximizedCapital(2, 1, [1, 2, 3], [1, 1, 2]) == 6
    print("All tests passed.")
K Closest Points To Origin â–¼ expand
import heapq
import random
from typing import List


class Solution:
    """
    ## 973. K Closest Points to Origin

    Given an array of `points` where `points[i] = [xi, yi]` represents a point on the X-Y plane
    and an integer `k`, return the `k` closest points to the origin `(0, 0)`.

    The distance between two points on the X-Y plane is the Euclidean distance:
    `sqrt((x1 - x2)^2 + (y1 - y2)^2)`.

    You may return the answer in **any order**. The answer is **guaranteed** to be unique
    (except for the order that it is in).

    ### Examples

    ```text
    Input: points = [[1, 3], [-2, 2]], k = 1
    Output: [[-2, 2]]

    Explanation:
        Distance of [1,3] from origin = sqrt(10) ≈ 3.16
        Distance of [-2,2] from origin = sqrt(8) ≈ 2.83
        [-2,2] is closer, so return [[-2,2]].
    ```

    ```text
    Input: points = [[3, 3], [5, -1], [-2, 4]], k = 2
    Output: [[3, 3], [-2, 4]]
    ```

    ### Constraints
    - `1 <= k <= points.length <= 10^4`
    - `-10^4 <= xi, yi <= 10^4`
    """

    def kClosest_sort(self, points: List[List[int]], k: int) -> List[List[int]]:
        # O(n log n) — sort all points by squared distance
        points.sort(key=lambda p: p[0] ** 2 + p[1] ** 2)
        return points[:k]

    def kClosest_heap(self, points: List[List[int]], k: int) -> List[List[int]]:
        # O(n log k) — max-heap of size k using negated distances
        heap: List = []
        for x, y in points:
            dist = -(x * x + y * y)
            heapq.heappush(heap, (dist, x, y))
            if len(heap) > k:
                heapq.heappop(heap)
        return [[x, y] for _, x, y in heap]

    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        # Use max-heap of size k; evict farthest point when heap exceeds k
        # No need to sort — heap maintains k closest points
        # O(n) average — quickselect partitioning by squared distance
        def dist(p):
            return p[0] ** 2 + p[1] ** 2

        def partition(lo, hi):
            pivot = dist(points[hi])
            i = lo
            for j in range(lo, hi):
                if dist(points[j]) <= pivot:
                    points[i], points[j] = points[j], points[i]
                    i += 1
            points[i], points[hi] = points[hi], points[i]
            return i

        lo, hi = 0, len(points) - 1
        while lo < hi:
            mid = partition(lo, hi)
            if mid == k - 1:
                break
            elif mid < k - 1:
                lo = mid + 1
            else:
                hi = mid - 1
        return points[:k]


if __name__ == '__main__':
    s = Solution()

    res = s.kClosest_sort([[1, 3], [-2, 2]], 1)
    assert res == [[-2, 2]]

    res = s.kClosest_heap([[1, 3], [-2, 2]], 1)
    assert sorted(res) == [[-2, 2]]

    res = s.kClosest([[1, 3], [-2, 2]], 1)
    assert res == [[-2, 2]]

    res = s.kClosest([[3, 3], [5, -1], [-2, 4]], 2)
    assert sorted(res) == [[-2, 4], [3, 3]]

    print("All tests passed.")
Kth Largest Element In A Stream â–¼ expand
import heapq
from typing import List


class KthLargest:
    """
    ## 703. Kth Largest Element in a Stream

    Design a class to find the `k`th largest element in a stream.
    Note that it is the `k`th largest element in the sorted order, not the `k`th distinct element.

    Implement the `KthLargest` class:
    - `KthLargest(int k, int[] nums)` — initializes the object with `k` and the stream `nums`.
    - `int add(int val)` — appends `val` to the stream and returns the element representing
      the `k`th largest element in the stream.

    ### Examples

    ```text
    Input:
        KthLargest(3, [4, 5, 8, 2])
        add(3) -> 4
        add(5) -> 5
        add(10) -> 8
        add(9) -> 8
        add(4) -> 8

    Explanation:
        After init: stream = [2, 4, 5, 8], k=3 → 3rd largest = 4
        add(3): stream = [2, 3, 4, 5, 8] → 3rd largest = 4
        add(5): stream = [2, 3, 4, 5, 5, 8] → 3rd largest = 5
    ```

    ### Constraints
    - `1 <= k <= 10^4`
    - `0 <= nums.length <= 10^4`
    - `-10^4 <= nums[i] <= 10^4`
    - `-10^4 <= val <= 10^4`
    - At most `10^4` calls to `add`
    - It is guaranteed that there will be at least `k` elements in the array when `add` is called.
    """

    # --- Approach 1: Sort on every add ---

    def __init__sort(self, k: int, nums: List[int]):
        # O(n log n) init
        self.k = k
        self.nums = sorted(nums)

    def add_sort(self, val: int) -> int:
        # O(n log n) per add — insert and re-sort
        import bisect
        bisect.insort(self.nums, val)
        return self.nums[-self.k]

    # --- Approach 2: Min-heap of size k ---

    def __init__(self, k: int, nums: List[int]):
        # Maintain a min-heap of size k; top is always the k-th largest
        # O(n log k) init
        self.k = k
        self.heap: List[int] = []
        for n in nums:
            heapq.heappush(self.heap, n)
            if len(self.heap) > k:
                heapq.heappop(self.heap)

    def add(self, val: int) -> int:
        # Push new val; if heap exceeds k, pop the smallest (not in top-k)
        # Top of min-heap is the k-th largest element
        # O(log k) per add — maintain min-heap of size k
        heapq.heappush(self.heap, val)
        if len(self.heap) > self.k:
            heapq.heappop(self.heap)
        return self.heap[0]


if __name__ == '__main__':
    # stream after init: [2,4,5,8], k=3 → 3rd largest = 4
    obj = KthLargest(3, [4, 5, 8, 2])
    assert obj.add(3) == 4   # [2,3,4,5,8] → 3rd largest = 4
    assert obj.add(5) == 5   # [2,3,4,5,5,8] → 3rd largest = 5
    assert obj.add(10) == 5  # [2,3,4,5,5,8,10] → 3rd largest = 5
    assert obj.add(9) == 8   # [2,3,4,5,5,8,9,10] → 3rd largest = 8
    assert obj.add(4) == 8   # [2,3,4,4,5,5,8,9,10] → 3rd largest = 8

    obj2 = KthLargest(1, [])
    assert obj2.add(-3) == -3
    assert obj2.add(-2) == -2
    assert obj2.add(-4) == -2

    print("All tests passed.")
Kth Largest Element In An Array â–¼ expand
import heapq
from typing import List


class Solution:
    """
    ## 215. Kth Largest Element in an Array

    Given an integer array `nums` and an integer `k`, return the `k`th largest element in the array.

    Note that it is the `k`th largest element in the sorted order, not the `k`th distinct element.

    You must solve it without sorting (for the optimal solution).

    ### Examples

    ```text
    Input: nums = [3, 2, 1, 5, 6, 4], k = 2
    Output: 5
    ```

    ```text
    Input: nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
    Output: 4
    ```

    ### Constraints
    - `1 <= k <= nums.length <= 10^5`
    - `-10^4 <= nums[i] <= 10^4`
    """

    def findKthLargest_sort(self, nums: List[int], k: int) -> int:
        # O(n log n) — sort descending and index
        return sorted(nums, reverse=True)[k - 1]

    def findKthLargest_heap(self, nums: List[int], k: int) -> int:
        # O(n log k) — min-heap of size k
        heap: List[int] = []
        for n in nums:
            heapq.heappush(heap, n)
            if len(heap) > k:
                heapq.heappop(heap)
        return heap[0]

    def findKthLargest(self, nums: List[int], k: int) -> int:
        # Min-heap of size k: after processing all elements, top is k-th largest
        # O(n) average — quickselect; target index in sorted-asc = len-k
        target = len(nums) - k

        def partition(lo, hi):
            pivot, fill = nums[hi], lo
            for i in range(lo, hi):
                if nums[i] <= pivot:
                    nums[fill], nums[i] = nums[i], nums[fill]
                    fill += 1
            nums[fill], nums[hi] = nums[hi], nums[fill]
            return fill

        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = partition(lo, hi)
            if mid == target:
                break
            elif mid < target:
                lo = mid + 1
            else:
                hi = mid - 1
        return nums[target]


if __name__ == '__main__':
    s = Solution()
    assert s.findKthLargest_sort([3, 2, 1, 5, 6, 4], 2) == 5
    assert s.findKthLargest_heap([3, 2, 1, 5, 6, 4], 2) == 5
    assert s.findKthLargest([3, 2, 1, 5, 6, 4], 2) == 5

    assert s.findKthLargest_sort([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4
    assert s.findKthLargest_heap([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4
    assert s.findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4

    print("All tests passed.")
Last Stone Weight â–¼ expand
import heapq
from typing import List


class Solution:
    """
    ## 1046. Last Stone Weight

    You are given an array of integers `stones` where `stones[i]` is the weight of the `i`th stone.

    We are playing a game with the stones. On each turn, we choose the **two heaviest** stones and
    smash them together. Suppose the heaviest two stones have weights `x` and `y` with `x <= y`:
    - If `x == y`, both stones are destroyed.
    - If `x != y`, the stone of weight `x` is destroyed, and the stone of weight `y` has new weight `y - x`.

    At the end of the game, there is **at most one** stone left. Return the weight of the last remaining
    stone. If there are no stones left, return `0`.

    ### Examples

    ```text
    Input: stones = [2, 7, 4, 1, 8, 1]
    Output: 1

    Explanation:
        Smash 8 and 7 → 1. Stones: [2, 4, 1, 1, 1]
        Smash 4 and 2 → 2. Stones: [2, 1, 1, 1]
        Smash 2 and 1 → 1. Stones: [1, 1, 1]
        Smash 1 and 1 → 0. Stones: [1]
        Result: 1
    ```

    ```text
    Input: stones = [1]
    Output: 1
    ```

    ### Constraints
    - `1 <= stones.length <= 30`
    - `1 <= stones[i] <= 1000`
    """

    def lastStoneWeight_sort(self, stones: List[int]) -> int:
        # O(n^2 log n) — sort on every iteration
        stones = sorted(stones)
        while len(stones) > 1:
            y, x = stones.pop(), stones.pop()
            if x != y:
                import bisect
                bisect.insort(stones, y - x)
        return stones[0] if stones else 0

    def lastStoneWeight(self, stones: List[int]) -> int:
        # Use max-heap (negate for Python's min-heap)
        # Repeatedly smash two heaviest; push remainder if non-zero
        # O(n log n) — max-heap via negation
        heap = [-s for s in stones]
        heapq.heapify(heap)
        while len(heap) > 1:
            y = -heapq.heappop(heap)
            x = -heapq.heappop(heap)
            if x != y:
                heapq.heappush(heap, -(y - x))
        return -heap[0] if heap else 0


if __name__ == '__main__':
    s = Solution()
    assert s.lastStoneWeight([2, 7, 4, 1, 8, 1]) == 1
    assert s.lastStoneWeight([1]) == 1
    assert s.lastStoneWeight([2, 2]) == 0
    assert s.lastStoneWeight_sort([2, 7, 4, 1, 8, 1]) == 1
    assert s.lastStoneWeight_sort([1]) == 1
    print("All tests passed.")
Longest Happy String â–¼ expand
import heapq


class Solution:
    """
    ## 1405. Longest Happy String

    A string `s` is called **happy** if it satisfies all of the following conditions:
    - `s` only contains the letters `'a'`, `'b'`, and `'c'`.
    - `s` does not contain `"aaa"`, `"bbb"`, or `"ccc"` as a substring.
    - `s` contains **at most** `a` occurrences of the letter `'a'`.
    - `s` contains **at most** `b` occurrences of the letter `'b'`.
    - `s` contains **at most** `c` occurrences of the letter `'c'`.

    Given three integers `a`, `b`, and `c`, return the **longest possible happy string**.
    If there are multiple longest happy strings, return any of them. If there is no such string,
    return the empty string `""`.

    ### Examples

    ```text
    Input: a = 1, b = 1, c = 7
    Output: "ccbccacc"
    ```

    ```text
    Input: a = 2, b = 2, c = 1
    Output: "aabbc" (or any valid)
    ```

    ```text
    Input: a = 7, b = 1, c = 0
    Output: "aabaa"
    ```

    ### Constraints
    - `0 <= a, b, c <= 100`
    - `a + b + c > 0`
    """

    def longestDiverseString(self, a: int, b: int, c: int) -> str:
        # Greedily pick the most frequent character that doesn't create 3 consecutive
        # If top char would create 3 in a row, use second most frequent instead
        # Re-add characters to heap after use if count > 0
        # O(a+b+c) — greedy: always pick most frequent char, limited to 2 consecutive
        heap = []
        for cnt, ch in [(-a, 'a'), (-b, 'b'), (-c, 'c')]:
            if cnt < 0:
                heapq.heappush(heap, (cnt, ch))

        result = []
        while heap:
            cnt, ch = heapq.heappop(heap)
            # If last two chars are same as current, use second most frequent
            if len(result) >= 2 and result[-1] == result[-2] == ch:
                if not heap:
                    break
                cnt2, ch2 = heapq.heappop(heap)
                result.append(ch2)
                if cnt2 + 1 < 0:
                    heapq.heappush(heap, (cnt2 + 1, ch2))
                heapq.heappush(heap, (cnt, ch))
            else:
                result.append(ch)
                if cnt + 1 < 0:
                    heapq.heappush(heap, (cnt + 1, ch))

        return ''.join(result)


if __name__ == '__main__':
    s = Solution()

    def is_happy(res, a, b, c):
        if 'aaa' in res or 'bbb' in res or 'ccc' in res:
            return False
        if res.count('a') > a or res.count('b') > b or res.count('c') > c:
            return False
        return True

    res = s.longestDiverseString(1, 1, 7)
    assert is_happy(res, 1, 1, 7) and len(res) == 8, f"Got '{res}'"

    res = s.longestDiverseString(7, 1, 0)
    assert is_happy(res, 7, 1, 0) and len(res) == 5, f"Got '{res}'"

    res = s.longestDiverseString(2, 2, 1)
    assert is_happy(res, 2, 2, 1) and len(res) == 5, f"Got '{res}'"

    print("All tests passed.")
Reorganize String â–¼ expand
import heapq
from collections import Counter


class Solution:
    """
    ## 767. Reorganize String

    Given a string `s`, rearrange the characters of `s` so that any two adjacent characters are
    not the same. Return any possible rearrangement of `s` or return `""` if not possible.

    ### Examples

    ```text
    Input: s = "aab"
    Output: "aba"
    ```

    ```text
    Input: s = "aaab"
    Output: ""
    ```

    ```text
    Input: s = "vvvlo"
    Output: "vlvov" (or any valid rearrangement)
    ```

    ### Constraints
    - `1 <= s.length <= 500`
    - `s` consists of lowercase English letters.
    """

    def reorganizeString(self, s: str) -> str:
        # Always place the most frequent remaining character
        # Use max-heap; after placing, cool down for one step before re-adding
        # O(n log k) where k=26 — greedy: always place most frequent char not equal to last placed
        counts = Counter(s)
        heap = [(-cnt, ch) for ch, cnt in counts.items()]
        heapq.heapify(heap)

        result = []
        prev_cnt, prev_ch = 0, ''

        while heap:
            cnt, ch = heapq.heappop(heap)
            result.append(ch)
            # Re-push previous character now that it's no longer adjacent
            if prev_cnt < 0:
                heapq.heappush(heap, (prev_cnt, prev_ch))
            prev_cnt, prev_ch = cnt + 1, ch  # cnt is negative, so +1 decrements

        return ''.join(result) if len(result) == len(s) else ''


if __name__ == '__main__':
    s = Solution()

    res = s.reorganizeString("aab")
    assert res == "aba", f"Got {res}"

    res = s.reorganizeString("aaab")
    assert res == ""

    res = s.reorganizeString("a")
    assert res == "a"

    # Validate no two adjacent chars are same for longer inputs
    res = s.reorganizeString("vvvlo")
    assert res and all(res[i] != res[i + 1] for i in range(len(res) - 1))

    res = s.reorganizeString("aabbcc")
    assert res and all(res[i] != res[i + 1] for i in range(len(res) - 1))

    print("All tests passed.")
Single Threaded Cpu â–¼ expand
import heapq
from typing import List


class Solution:
    """
    ## 1834. Single-Threaded CPU

    You are given `n` tasks labeled from `0` to `n - 1` represented by a 2D integer array `tasks`,
    where `tasks[i] = [enqueueTimei, processingTimei]` represents the time at which the `i`th task
    will be available for processing and the time it takes to finish processing.

    Return the **order** in which the CPU will process the tasks.

    Rules:
    - If the CPU is idle and there are no available tasks, the CPU waits until the next available task.
    - If the CPU is idle and there are available tasks, the CPU picks the task with the **shortest
      processing time**. If multiple tasks have the same processing time, it picks the task with the
      **smallest index**.
    - Once a task is started, the CPU runs it to completion.

    ### Examples

    ```text
    Input: tasks = [[1,2],[2,4],[3,2],[4,1]]
    Output: [0, 2, 3, 1]

    Explanation:
        t=1: task 0 available, CPU picks task 0 (proc=2), finishes at t=3
        t=3: tasks 1,2 available; pick task 2 (proc=2, shorter than 4), finishes at t=5
        t=5: tasks 1,3 available; pick task 3 (proc=1), finishes at t=6
        t=6: task 1 available; pick task 1, finishes at t=10
    ```

    ```text
    Input: tasks = [[7,10],[7,12],[7,5],[7,4],[7,2]]
    Output: [4, 3, 2, 0, 1]
    ```

    ### Constraints
    - `1 <= tasks.length <= 10^5`
    - `1 <= enqueueTimei, processingTimei <= 10^9`
    """

    def getOrder(self, tasks: List[List[int]]) -> List[int]:
        # Sort tasks by enqueue time; simulate CPU with a min-heap of (processing_time, index)
        # At each step, advance time to next available task if CPU is idle
        # Always process the shortest available task (min-heap by processing time, then index)
        # O(n log n) — sort by enqueue time, simulate with min-heap on (proc_time, index)
        indexed = sorted(enumerate(tasks), key=lambda x: x[1][0])
        heap: List = []  # (processing_time, original_index)
        result = []
        time = 0
        i = 0
        n = len(tasks)

        while len(result) < n:
            # Push all tasks available at current time
            while i < n and indexed[i][1][0] <= time:
                orig_idx, task = indexed[i]
                heapq.heappush(heap, (task[1], orig_idx))
                i += 1

            if heap:
                proc_time, orig_idx = heapq.heappop(heap)
                result.append(orig_idx)
                time += proc_time
            else:
                # CPU idle — jump to next task's enqueue time
                time = indexed[i][1][0]

        return result


if __name__ == '__main__':
    s = Solution()
    assert s.getOrder([[1, 2], [2, 4], [3, 2], [4, 1]]) == [0, 2, 3, 1]
    assert s.getOrder([[7, 10], [7, 12], [7, 5], [7, 4], [7, 2]]) == [4, 3, 2, 0, 1]
    assert s.getOrder([[1, 1], [1, 1]]) == [0, 1]
    print("All tests passed.")
Task Scheduler â–¼ expand
import heapq
from collections import Counter, deque
from typing import List


class Solution:
    """
    ## 621. Task Scheduler

    Given a characters array `tasks`, representing the tasks a CPU needs to do, where each letter
    represents a different task. Tasks could be done in any order. Each task is done in one unit of
    time. For each unit of time, the CPU could complete either one task or just be idle.

    However, there is a non-negative integer `n` that represents the cooldown interval between two
    **same tasks** (the same letter in the array), that is that there must be at least `n` units of
    time between any two same tasks.

    Return the **least number of units of times** that the CPU will take to finish all the given tasks.

    ### Examples

    ```text
    Input: tasks = ["A","A","A","B","B","B"], n = 2
    Output: 8

    Explanation: A -> B -> idle -> A -> B -> idle -> A -> B
    ```

    ```text
    Input: tasks = ["A","A","A","B","B","B"], n = 0
    Output: 6
    ```

    ```text
    Input: tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
    Output: 16
    ```

    ### Constraints
    - `1 <= tasks.length <= 10^4`
    - `tasks[i]` is an uppercase English letter.
    - `0 <= n <= 100`
    """

    def leastInterval_heap(self, tasks: List[str], n: int) -> int:
        # O(t * n) — simulate with max-heap and cooldown queue
        counts = Counter(tasks)
        heap = [-c for c in counts.values()]
        heapq.heapify(heap)
        time = 0
        cooldown: deque = deque()  # (available_time, neg_count)
        while heap or cooldown:
            time += 1
            if heap:
                cnt = heapq.heappop(heap) + 1  # decrement count
                if cnt < 0:
                    cooldown.append((time + n, cnt))
            if cooldown and cooldown[0][0] == time:
                heapq.heappush(heap, cooldown.popleft()[1])
        return time

    def leastInterval(self, tasks: List[str], n: int) -> int:
        # Most frequent task determines idle time needed
        # Formula: (max_freq - 1) * (n + 1) + count_of_max_freq_tasks
        # But if tasks fill all slots, answer is just len(tasks)
        # O(t) — math formula based on most frequent task
        counts = Counter(tasks)
        max_count = max(counts.values())
        num_max = sum(1 for c in counts.values() if c == max_count)
        return max(len(tasks), (max_count - 1) * (n + 1) + num_max)


if __name__ == '__main__':
    s = Solution()
    assert s.leastInterval_heap(["A", "A", "A", "B", "B", "B"], 2) == 8
    assert s.leastInterval(["A", "A", "A", "B", "B", "B"], 2) == 8

    assert s.leastInterval_heap(["A", "A", "A", "B", "B", "B"], 0) == 6
    assert s.leastInterval(["A", "A", "A", "B", "B", "B"], 0) == 6

    assert s.leastInterval(["A", "A", "A", "A", "A", "A", "B", "C", "D", "E", "F", "G"], 2) == 16

    print("All tests passed.")

Heap Visualizer

Insert (sift-up) and Extract-Min (sift-down)

3851210
Array:3851210
Min-Heap. Insert a value or Extract Min to see sift operations.