Название задания
Описание
Дан массив чисел nums и целое число k. Необходимо вернуть k самых часто встречающихся эллементов. Вернуть значения можно в любом порядке. Число k всегда меньше или равно количеству уникальных значений в nums 
Пример 1:
Входные данные: nums = [1, 1, 1, 2, 2, 3], k = 2
Выходные данные: [1, 2]
Объяснение: Числа по частоте вхождения: [1, 2, 3] - возвращаем первые 2 числа
Пример 2:
Входные данные: nums = [1], k = 1
Выходные данные: [1]
Пример 3:
Входные данные: nums = [1, 2, 3, 2], k = 1
Выходные данные: [2]
Идея решения
Увидев эту задачу в первый раз я подумал, что хитрое решение через приоритетные очереди будет эффективным. Оказалось, что решение через сортировку более эффективное, его и разберём, а второе решение я тоже добавлю ниже.
В целом оба подхода похожи. Сначала мы считаем количество вхождений каждого элемента. Задача была бы проще если бы нам нужно было вернуть только один, самый часто встречающийся элемент, но нам нужно k элементов, так что считаем все.
Посчитав вхождения, сортируем их по количеству вхождений и возвращаем первые k чисел.
Алгоритм решения
В первой части нашего алгоритма мы итерируем массив и инкрементируем значение для встречающихся эллементов. Такое уже было в другой задаче этого же раздела.
Во второй части мы сортируем наши значения, при этом сортировка нужно по убыванию значений, а не ключей.
Далее возвращаем k первых ключей.
Таким образом наш алгоритм выглядит так:
1. создаём и заполняем хэш-таблицу ключи - уникальные числа, значения - кол-во их вхождений в массив
2. приводим нашу таблицу к списку кортежей(ключ, значение) и сортируем его по значениям
3. сохраняем k чисел(ключи хэш-таблицы) в результатирующий список
4. возвращаем результат в любом порядке
Сложность
- По времени: \(O(N + U log U)\) N = len(nums), U = len(set(nums))
Подсчёт элементов требует обратиться к каждому числу один раз, встравка в хэш-таблицу занимает константное время.
Преобразование хэш-таблицы и выборка k элементов занимает в худшем случае \(O(U)\), где U - количество уникальных чисел в нашем массиве. Поскольку U всегда будет меньше чем N мы можем не учитывать этот этап, так как N мы уже учли в первой части. 
Значительное влияние на скорость оказывает сортировка, которая будет выполняться за \(O(U log U)\). Можно было бы оптимизировать алгоритм выполняя сортировку в момент вставки в словарь, но размер решения и читаемость кода сильно измениться при таком подходе.
- По памяти: \(O(U)\) U = len(set(nums))
При решении мы 4 раза выделяем память равную количеству уникальных элементов в массиве. 1 - для counted_nums, 2 - для counted_vec, 3 - для сортированного списка и 4 - для result. В упрощённом виде мы представляем это как \(O(U)\).
Код решения:
class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        counted_nums = {}
        for num in nums:
            counted_nums[num] = counted_nums.get(num, 0) + 1
        result = []
        counted_vec = counted_nums.items()
        for num, _ in sorted(counted_vec, key=lambda x: -x[1]):
            result.append(num)
            if len(result) >= k:
                break
        return result
use std::collections::{HashMap, BinaryHeap};
impl Solution {
    pub fn top_k_frequent(nums: Vec<i32>, k: i32) -> Vec<i32> {
        let mut counted_nums = HashMap::new();
        for num in nums {
            *counted_nums.entry(num).or_insert(1) += 1;
        }
        let mut result: Vec<i32> = Vec::with_capacity(k as usize);
        let mut counted_vec: Vec<(i32, i32)> = counted_nums.into_iter().collect();
        counted_vec.sort_by(|a, b| b.1.cmp(&a.1));
        for (num, _) in counted_vec.iter() {
            result.push(*num);
            if result.len() >= k as usize {
                break;
            }
        }
        result
    }
} 
use std::collections::{HashMap, BinaryHeap};
impl Solution {
    pub fn top_k_frequent(nums: Vec<i32>, k: i32) -> Vec<i32> {
        let mut counted_nums = HashMap::new();
        for num in nums {
            *counted_nums.entry(num).or_insert(0) += 1;
        }
        let mut heap = BinaryHeap::new();
        for (num, count) in counted_nums {
            heap.push((count, num));
        }
        let mut result = Vec::with_capacity(k as usize);
        for _ in 0..k {
            result.push(heap.pop().unwrap().1);
        }
        result
    }
}    
В Python используется мин-куча, в связи с чем сохраняются данные с отрицательным знаком.