Heaps / Priority Queue Flashcards

1
Q
  1. K Closest Points to Origin
    Solved
    Medium

Topics

Companies Facebook
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 (i.e., √(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).

Example 1:

Input: points = [[1,3],[-2,2]], k = 1
Output: [[-2,2]]
Explanation:
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]].
Example 2:

Input: points = [[3,3],[5,-1],[-2,4]], k = 2
Output: [[3,3],[-2,4]]
Explanation: The answer [[-2,4],[3,3]] would also be accepted.

Constraints:

1 <= k <= points.length <= 104
-104 <= xi, yi <= 104

https://leetcode.com/problems/k-closest-points-to-origin/description/

A
class Solution(object):
    def kClosest(self, points, k):
        """
        :type points: List[List[int]]
        :type k: int
        :rtype: List[List[int]]
        """
        def euc(x,y):
            return math.sqrt(x**2 + y**2)

        heap = []
        for idx, point in enumerate(points):
            x,y = point[0], point[1]
            dis = euc(x,y)
            heapq.heappush(heap, (-dis, idx))
            if len(heap) > k:
                heapq.heappop(heap)
        return [points[res[1]] for res in heap]

O(nLog(k)), Space: K

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q
  1. Task Scheduler
    Solved
    Medium

Topics

Companies

Hint
You are given an array of CPU tasks, each represented by letters A to Z, and a cooling time, n. Each cycle or interval allows the completion of one task. Tasks can be completed in any order, but there’s a constraint: identical tasks must be separated by at least n intervals due to cooling time.

​Return the minimum number of intervals required to complete all tasks.

Example 1:

Input: tasks = [“A”,”A”,”A”,”B”,”B”,”B”], n = 2

Output: 8

Explanation: A possible sequence is: A -> B -> idle -> A -> B -> idle -> A -> B.

After completing task A, you must wait two cycles before doing A again. The same applies to task B. In the 3rd interval, neither A nor B can be done, so you idle. By the 4th cycle, you can do A again as 2 intervals have passed.

Example 2:

Input: tasks = [“A”,”C”,”A”,”B”,”D”,”B”], n = 1

Output: 6

Explanation: A possible sequence is: A -> B -> C -> D -> A -> B.

With a cooling interval of 1, you can repeat a task after just one other task.

Example 3:

Input: tasks = [“A”,”A”,”A”, “B”,”B”,”B”], n = 3

Output: 10

Explanation: A possible sequence is: A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B.

There are only two types of tasks, A and B, which need to be separated by 3 intervals. This leads to idling twice between repetitions of these tasks.

Constraints:

1 <= tasks.length <= 104
tasks[i] is an uppercase English letter.
0 <= n <= 100

https://leetcode.com/problems/task-scheduler/description/

A

O(nLog(k)) solution where k is number of unique tasks. n is total time required, k will never go above len(tasks)

from collections import Counter
class Solution(object):
    def leastInterval(self, tasks, n):
        """
        :type tasks: List[str]
        :type n: int
        :rtype: int
        """
        counts = Counter(tasks)
        heap = [-val for val in counts.values()] # The execution queue, the highest count tasks execute first.
        heapq.heapify(heap)
        time = 0
        q = deque() # The waiting queue
        while heap or q:
            # is there any task pending for execution at this time?
            if q and q[0][0] == time:
                # Add the task to execution queue
                heapq.heappush(heap, q[0][1])
                q.popleft()
            if heap:
                # There are tasks waiting to be executed
                # pick one, execute it and append the time for the next time it can be executed if needed.
                remaining = 1 + heapq.heappop(heap)
                if remaining:
                    q.append((time+(n+1), remaining))
            time+=1
        return time

Simpler Solution O(n) but does use a lot of edge cases, I do not like this solution as in interview might not be possible to cleanly execute this. Heap solution is better for interview.

from collections import Counter
class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        counts = Counter(tasks)
        sortedCounts = sorted(list(counts.keys()), key=counts.get)
        maxFreq = counts[sortedCounts[-1]]
        # Imagine 3 A's and n = 2
        # idle slots = A Idle Idle A Idle Idle A = 4 i.e count - 1 * n
        idleSlots = (maxFreq - 1) * n
        for task in sortedCounts[:-1]:
            # min because there can be entries with same counts as max freq
            # thus just like the above their last entry will need no idle slots as its the last one
            # which will be after the maxFreq task. 
            # eg. A A A B B B CC ,  n = 3   A B C I A B C I A B 
            # notice how last A and B need n-1 idle slots 
            idleSlots -= min(maxFreq-1, counts[task])
        # if no idle slots remain then just return the len of tasks
        # else add the idle slots to len of tasks. 
        # if n is low enough, there will be no idle slots, rather 
        # we will be short of idle slots, so just take max of 0, idleslots
        return len(tasks) + max(0, idleSlots)

heap solution is preferred in interview, but the O(n) might be asked if

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q
  1. Find Median from Data Stream
    Solved
    Hard

Topics

Companies:
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.

For example, for arr = [2,3,4], the median is 3.
For example, for arr = [2,3], the median is (2 + 3) / 2 = 2.5.
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.

Example 1:

Input
[“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”]
[[], [1], [2], [], [3], []]
Output
[null, null, null, 1.5, null, 2.0]

Explanation
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1); // arr = [1]
medianFinder.addNum(2); // arr = [1, 2]
medianFinder.findMedian(); // return 1.5 (i.e., (1 + 2) / 2)
medianFinder.addNum(3); // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

Constraints:

-105 <= num <= 105
There will be at least one element in the data structure before calling findMedian.
At most 5 * 104 calls will be made to addNum and findMedian.

Follow up:

If all integer numbers from the stream are in the range [0, 100], how would you optimize your solution?
If 99% of all integer numbers from the stream are in the range [0, 100], how would you optimize your solution?

https://leetcode.com/problems/find-median-from-data-stream/

A

Always push in max heap (smaller half), then pop the largest of smal half and push it in larger half (min heap). Then balance them if needed. Since we always push the value in min_heap, (large half) we check if that’s larger.

class MedianFinder:

    def \_\_init\_\_(self):
        self.min_heap = []
        self.max_heap = []

    def push_max_heap(self, val):
	    heapq.heappush(self.max_heap, -val)
    def pop_max_heap(self):
	    return -(heapq.heappop(self.max_heap))
    def peek_max_heap(self):
	    return -(self.max_heap[0])

    def addNum(self, num: int) -> None:
        self.push_max_heap(num)
        heapq.heappush(self.min_heap, self.pop_max_heap())
        if len(self.min_heap) > len(self.max_heap) + 1:
            val = heapq.heappop(self.min_heap)
            self.push_max_heap(val)

    def findMedian(self) -> float:
        if len(self.max_heap) == len(self.min_heap):
            return (self.peek_max_heap() + self.min_heap[0])/2.0
        elif len(self.max_heap) > len(self.min_heap):
            return self.peek_max_heap()
        else:
            return self.min_heap[0]
class MinHeap:
    def \_\_init\_\_(self):
        self.base = []
    
    def insert(self, val):
        heapq.heappush(self.base, val)

    def pop(self):
        if self.base:
            return heapq.heappop(self.base)
        else:
            return None
    
    def peek(self):
        if self.base:
            return self.base[0]
        else:
            None
    def size(self):
        return len(self.base)
    
    def empty(self):
        return True if len(self.base) == 0 else False

class MaxHeap:
    def \_\_init\_\_(self):
        self.base = []
    
    def insert(self, val):
        heapq.heappush(self.base, -1 * val)

    def pop(self):
        if self.base:
            return -1 * heapq.heappop(self.base)
        else:
            return None
    
    def peek(self):
        if self.base:
            return -1 * self.base[0]
        else:
            None

    def size(self):
        return len(self.base)

    def empty(self):
        return True if len(self.base) == 0 else False
class MedianFinder(object):
    def \_\_init\_\_(self):
        self.small = MaxHeap() # for smaller half
        self.large = MinHeap() # for larger half

    def addNum(self, num):
        """
        :type num: int
        :rtype: None
        """
        self.small.insert(num)
        self.large.insert(self.small.pop())
        if self.large.size() > self.small.size() + 1:
            self.small.insert(self.large.pop())
        
    def findMedian(self):
        """
        :rtype: float
        """
        if self.small.size() > self.large.size():
            return self.small.peek()
        elif self.large.size() > self.small.size():
            return self.large.peek()
        elif self.large.empty():
            return 0
        else:
            return (self.large.peek() + self.small.peek()) / 2.0

overused question, don’t expect in interview, but good to know how to use 2 heaps

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q
  1. Reorganize String
    Solved
    Medium

Topics

Companies

Hint
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.

Example 1:

Input: s = “aab”
Output: “aba”
Example 2:

Input: s = “aaab”
Output: “”

Constraints:

1 <= s.length <= 500
s consists of lowercase English letters.

https://leetcode.com/problems/reorganize-string/description/

A

Same as the Leetcode 621

from collections import deque, defaultdict
class Solution(object):
def reorganizeString(self, s):
“””
:type s: str
:rtype: str
“””
heap = []
q = deque()
charCount = defaultdict(int)
for char in s:
charCount[char] -= 1
for key, value in charCount.items():
heapq.heappush(heap, (value, key))
pos = 0
ret = “”
while heap or q:
pos += 1
if heap:
freq, key = heapq.heappop(heap)
freq += 1
if freq <= 0:
q.append((key, pos + 1, freq))
if q and q[0][1] == pos:
key, pos, freq = q.popleft()
if ret and ret[-1] == key:
return “”
ret += key
heapq.heappush(heap, (freq, key))
return ret

O(n) solution, place the max freq char first, and then start placing the other ones

from collections import Counter

class Solution:
def reorganizeString(self, s: str) -> str:
output = [””] * len(s)
counts = Counter(s)
maxChar = ‘’
maxCharFreq = 0
maxChar, maxCharFreq = max(counts.items(), key=lambda x: x[1])
if maxCharFreq > (len(s) + 1) // 2:
return “”
idx = 0
while counts[maxChar] > 0:
output[idx] = maxChar
idx += 2
counts[maxChar] -= 1
for char, freq in counts.items():
while freq > 0:
if idx >= len(s):
idx = 1
output[idx] = char
idx += 2
freq -= 1
return ““.join(output)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q
  1. Top K Frequent Elements

https://leetcode.com/problems/top-k-frequent-elements/description/347. Top K Frequent Elements
Solved
Medium
Topics
Companies
Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.

Example 1:

Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:

Input: nums = [1], k = 1
Output: [1]

Constraints:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
k is in the range [1, the number of unique elements in the array].
It is guaranteed that the answer is unique.

Follow up: Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size.

Facebook
58
Amazon
20

A

from collections import Counter

class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
if k == len(nums):
return nums
counts = Counter(nums)
return heapq.nlargest(k, counts.keys(), counts.get)

using quick select with count frequencies as key.
from collections import Counter
class Solution(object):
def topKFrequent(self, nums, k):
“””
:type nums: List[int]
:type k: int
:rtype: List[int]
“””

    counts = Counter(nums)
    unique = counts.keys()

    def partition(l, r):
        if l == r:
            return l
        pivot = unique[r]
        store_idx = l

        for i in range(l, r):
            if counts[unique[i]] < counts[pivot]:
                unique[i], unique[store_idx] = unique[store_idx], unique[i]
                store_idx += 1
        unique[r], unique[store_idx] = unique[store_idx], unique[r]

        return store_idx
    
    def quick_select(k):
        k = len(unique) - k
        l = 0
        r = len(unique) - 1
        piv_idx = partition(l, r)
        while l < r:
            if piv_idx == k:
                return
            elif piv_idx < k:
                l = piv_idx + 1
                piv_idx = partition(l, r)
            else:
                r = piv_idx - 1
                piv_idx = partition(l, r)

    quick_select(k)
    n = len(unique)
    return unique[n-k:]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q
  1. Design A Leaderboard
    Solved
    Medium
    Topics
    Companies
    Hint
    Design a Leaderboard class, which has 3 functions:

addScore(playerId, score): Update the leaderboard by adding score to the given player’s score. If there is no player with such id in the leaderboard, add him to the leaderboard with the given score.
top(K): Return the score sum of the top K players.
reset(playerId): Reset the score of the player with the given id to 0 (in other words erase it from the leaderboard). It is guaranteed that the player was added to the leaderboard before calling this function.
Initially, the leaderboard is empty.

Example 1:

Input:
[“Leaderboard”,”addScore”,”addScore”,”addScore”,”addScore”,”addScore”,”top”,”reset”,”reset”,”addScore”,”top”]
[[],[1,73],[2,56],[3,39],[4,51],[5,4],[1],[1],[2],[2,51],[3]]
Output:
[null,null,null,null,null,null,73,null,null,null,141]

Explanation:
Leaderboard leaderboard = new Leaderboard ();
leaderboard.addScore(1,73); // leaderboard = [[1,73]];
leaderboard.addScore(2,56); // leaderboard = [[1,73],[2,56]];
leaderboard.addScore(3,39); // leaderboard = [[1,73],[2,56],[3,39]];
leaderboard.addScore(4,51); // leaderboard = [[1,73],[2,56],[3,39],[4,51]];
leaderboard.addScore(5,4); // leaderboard = [[1,73],[2,56],[3,39],[4,51],[5,4]];
leaderboard.top(1); // returns 73;
leaderboard.reset(1); // leaderboard = [[2,56],[3,39],[4,51],[5,4]];
leaderboard.reset(2); // leaderboard = [[3,39],[4,51],[5,4]];
leaderboard.addScore(2,51); // leaderboard = [[2,51],[3,39],[4,51],[5,4]];
leaderboard.top(3); // returns 141 = 51 + 51 + 39;

Constraints:

1 <= playerId, K <= 10000
It’s guaranteed that K is less than or equal to the current number of players.
1 <= score <= 100
There will be at most 1000 function calls.

https://leetcode.com/problems/design-a-leaderboard/

A

from collections import defaultdict
class Leaderboard(object):

def \_\_init\_\_(self):
    self.score = defaultdict(int)

def addScore(self, playerId, score):
    """
    :type playerId: int
    :type score: int
    :rtype: None
    """
    self.score[playerId] += score

def top(self, K):
    """
    :type K: int
    :rtype: int
    """
    heap = []
    for v in self.score.values():
        heapq.heappush(heap, v)
        if len(heap) > K:
            heapq.heappop(heap)
    return sum(heap)
    

def reset(self, playerId):
    """
    :type playerId: int
    :rtype: None
    """
    del self.score[playerId]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q
  1. Last Stone Weight
    Solved
    Easy
    Topics
    Companies
    Hint
    You are given an array of integers stones where stones[i] is the weight of the ith stone.

We are playing a game with the stones. On each turn, we choose the heaviest two stones and smash them together. Suppose the heaviest two stones have weights x and y with x <= y. The result of this smash is:

If x == y, both stones are destroyed, and
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.

Example 1:

Input: stones = [2,7,4,1,8,1]
Output: 1
Explanation:
We combine 7 and 8 to get 1 so the array converts to [2,4,1,1,1] then,
we combine 2 and 4 to get 2 so the array converts to [2,1,1,1] then,
we combine 2 and 1 to get 1 so the array converts to [1,1,1] then,
we combine 1 and 1 to get 0 so the array converts to [1] then that’s the value of the last stone.
Example 2:

Input: stones = [1]
Output: 1

Constraints:

1 <= stones.length <= 30
1 <= stones[i] <= 1000

https://leetcode.com/problems/last-stone-weight/description/

A

class Solution(object):
def lastStoneWeight(self, stones):
“””
:type stones: List[int]
:rtype: int
“””
s = [-s for s in stones]
heapq.heapify(s)
res = 0
while len(s) > 1:
heapq.heappush(s, (heapq.heappop(s) - heapq.heappop(s)))
return -1 * s[-1]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q
  1. K Closest Points to Origin
    Solved
    Medium
    Topics
    Companies
    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 (i.e., √(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).

Example 1:

Input: points = [[1,3],[-2,2]], k = 1
Output: [[-2,2]]
Explanation:
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest k = 1 points from the origin, so the answer is just [[-2,2]].
Example 2:

Input: points = [[3,3],[5,-1],[-2,4]], k = 2
Output: [[3,3],[-2,4]]
Explanation: The answer [[-2,4],[3,3]] would also be accepted.

Constraints:

1 <= k <= points.length <= 104
-104 <= xi, yi <= 104

https://leetcode.com/problems/k-closest-points-to-origin/

A

This is O(nLogK) solution)

from math import sqrt
class Solution:
def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
points = [(-sqrt(x[0]2 + x[1]2), x) for x in points]
heap = []
for negdist, point in points:
if heap:
if len(heap) == k and heap[0][0] < negdist:
heapq.heappop(heap)
heapq.heappush(heap, (negdist, point))
elif len(heap) < k:
heapq.heappush(heap, (negdist, point))
else:
heapq.heappush(heap, (negdist, point))
return [p[1] for p in heap]

The diff is that we keep heap size bounded. so after first K elements, its just logK for insert, remove
anything else will be O(nLog(n))

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q
  1. Kth Largest Element in an Array
    Solved
    Medium
    Topics
    Companies: Facebook
    Given an integer array nums and an integer k, return the kth largest element in the array.

Note that it is the kth largest element in the sorted order, not the kth distinct element.

Can you solve it without sorting?

Example 1:

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

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

Constraints:

1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104

https://leetcode.com/problems/kth-largest-element-in-an-array/description

A

class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
h = []
for elem in nums:
heapq.heappush(h, elem)
if len(h) > k:
heapq.heappop(h)
return h[0]

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q
  1. Merge k Sorted Lists
    Solved
    Hard
    Topics
    Companies

You are given an array of k linked-lists lists, each linked-list is sorted in ascending order.

Merge all the linked-lists into one sorted linked-list and return it.

Example 1:

Input: lists = [[1,4,5],[1,3,4],[2,6]]
Output: [1,1,2,3,4,4,5,6]
Explanation: The linked-lists are:
[
1->4->5,
1->3->4,
2->6
]
merging them into one sorted list:
1->1->2->3->4->4->5->6

Example 2:

Input: lists = []
Output: []

Example 3:

Input: lists = [[]]
Output: []

Constraints:

k == lists.length
0 <= k <= 104
0 <= lists[i].length <= 500
-104 <= lists[i][j] <= 104
lists[i] is sorted in ascending order.
The sum of lists[i].length will not exceed 104.

https://leetcode.com/problems/merge-k-sorted-lists/description/

A
# Definition for singly-linked list.
# class ListNode:
#     def \_\_init\_\_(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        sentinel = ListNode()
        cur = sentinel
        heap = []
        for i in range(len(lists)):
            if lists[i]:
                heapq.heappush(heap, (lists[i].val, i))

        while heap:
            _, idx = heapq.heappop(heap)
            cur.next = lists[idx]
            lists[idx] = lists[idx].next
            cur = cur.next
            if lists[idx]:
                heapq.heappush(heap, (lists[idx].val, idx))
        return sentinel.next
How well did you know this?
1
Not at all
2
3
4
5
Perfectly