Списки против массивов: когда что использовать

ОперацияМассив/СрезСвязный список
Доступ по индексуO(1)O(n)
ПоискO(n)O(n)
Вставка в началоO(n)O(1)
Вставка в конецO(1) амор.O(1) (с tail)
Вставка в серединуO(n)O(1) *
Удаление из началаO(n)O(1)
Удаление из серединыO(n)O(1) *
Память на элемент1 слово2–3 слова (+ ptr)
Кэш-дружественность✓ высокая✗ низкая

* при наличии указателя на узел

Массивы — непрерывная область памяти. Процессор загружает соседние элементы в кэш-линию (64 байта). Связный список — узлы разбросаны по heap, каждый переход по next — потенциальный cache miss.

На практике для n < 1000 массив быстрее списка даже для вставок в начало из-за кэш-эффекта.

✓ Частые вставки/удаления в середину при наличии указателя
✓ Размер неизвестен, данные не нужны по индексу
✓ Реализация очередей и деков (container/list)
✓ LRU-кэш (список + map)

✗ Случайный доступ по индексу
✗ Числовые вычисления и сортировка
✗ Небольшие наборы данных

Классическая комбинация: двусвязный список (порядок использования) + map (быстрый доступ):

type LRU[K comparable, V any] struct {
    cap   int
    list  *list.List
    index map[K]*list.Element
}

func (c *LRU[K, V]) Get(key K) (V, bool) {
    if e, ok := c.index[key]; ok {
        c.list.MoveToFront(e)
        return e.Value.(*entry[K, V]).val, true
    }
    var zero V
    return zero, false
}

ЗаданиеСложность
1Упражнение 1easy
2Упражнение 2easy
3Упражнение 3medium
4Упражнение 4medium