LC 0347 [M] Top K Frequent Elements - ALawliet/algorithms GitHub Wiki

hashmap + heap: O(nlogk)

from queue import PriorityQueue

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        num_to_freq = Counter(nums)
        minH = PriorityQueue(k+1)
        
        for num, freq in num_to_freq.items():
            minH.put( (freq, num) )
            if minH.full():
                minH.get()
                
        return list(map(lambda freq_num: freq_num[1], minH.queue))

bucket sort: O(n)

There are solution, using quickselect with O(n) complexity in average, but I think they are overcomplicated: actually, there is O(n) solution, using bucket sort. The idea, is that frequency of any element can not be more than n. So, the plan is the following:

Create list of empty lists for bucktes: for frequencies 1, 2, ..., n.
Use Counter to count frequencies of elements in nums
Iterate over our Counter and add elements to corresponding buckets.
buckets is list of lists now, create one big list out of it.
Finally, take the k last elements from this list, these elements will be top K frequent elements.

Complexity: time complexity is O(n), because we first iterate over nums once and create buckets, then we flatten list of lists with total number of elements O(n) and finally we return last k elements. Space complexity is also O(n).

print(buckets) # returns a list of lists
[], [3], [2], [1], [], [], [](/ALawliet/algorithms/wiki/],-[3],-[2],-[1],-[],-[],-[)

print(*buckets) # returns an iterator of lists
[] [3] [2] [1] [] [] []

print(chain(*buckets))
<itertools.chain object at 0x7f5af1513550>

print(list(chain(*buckets))) # chaining an iterator gets rid of empty lists and extends the items into one list
[3, 2, 1]

filled_buckets = []
for l in buckets:
    if l: # is not empty
        filled_buckets += l # filled_buckets.extend(l), add each item without the list
class Solution:
    def topKFrequent(self, nums, k):
        buckets = [[] for _ in range(len(nums) + 1)] # n + 1 buckets

        for num, freq in Counter(nums).items():
            buckets[freq].append(num) 

        filled_buckets = list(chain(*buckets))
        return filled_buckets[::-1][:k] # reversed up to k

quickselect: O(n) average

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        count = Counter(nums)
        unique = list(count.keys())
        
        def partition(left, right, pivot_index) -> int:
            pivot_frequency = count[unique[pivot_index]]
            # 1. move pivot to end
            unique[pivot_index], unique[right] = unique[right], unique[pivot_index]  
            
            # 2. move all less frequent elements to the left
            store_index = left
            for i in range(left, right):
                if count[unique[i]] < pivot_frequency:
                    unique[store_index], unique[i] = unique[i], unique[store_index]
                    store_index += 1

            # 3. move pivot to its final place
            unique[right], unique[store_index] = unique[store_index], unique[right]  
            
            return store_index
        
        def quickselect(left, right, k_smallest) -> None:
            """
            Sort a list within left..right till kth less frequent element
            takes its place. 
            """
            # base case: the list contains only one element
            if left == right: 
                return
            
            # select a random pivot_index
            pivot_index = random.randint(left, right)     
                            
            # find the pivot position in a sorted list   
            pivot_index = partition(left, right, pivot_index)
            
            # if the pivot is in its final sorted position
            if k_smallest == pivot_index:
                 return 
            # go left
            elif k_smallest < pivot_index:
                quickselect(left, pivot_index - 1, k_smallest)
            # go right
            else:
                quickselect(pivot_index + 1, right, k_smallest)
         
        n = len(unique) 
        # kth top frequent element is (n - k)th less frequent.
        # Do a partial sort: from less frequent to the most frequent, till
        # (n - k)th less frequent element takes its place (n - k) in a sorted array. 
        # All element on the left are less frequent.
        # All the elements on the right are more frequent.  
        quickselect(0, n - 1, n - k)
        # Return top k frequent elements
        return unique[n - k:]