← Назад к вопросам

Как в односвязном списке найти k-й элемент с конца?

1.7 Middle🔥 133 комментариев
#Коллекции и структуры данных

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Поиск k-го элемента с конца в односвязном списке

В контексте собеседования для C# Backend разработчика, задача поиска k-го элемента с конца односвязного списка является классической проверкой понимания базовых структур данных, алгоритмического мышления и владения языком C#.

Общая идея решения

Ключевая проблема заключается в том, что односвязный список позволяет двигаться только в одном направлении — от головы к хвосту. Прямой доступ к k-му элементу с конца невозможен без предварительного подсчета длины или использования двух указателей. Основные подходы:

1. Метод с подсчетом длины (два прохода)

  • Первый проход: подсчитываем общее количество элементов n.
  • Второй проход: двигаемся от головы к элементу с индексом n - k. Этот подход требует O(n) времени и O(1) дополнительной памяти.

2. Метод двух указателей (один проход — «runner technique»)

  • Используем два указателя — fast (быстрый) и slow (медленный).
  • Шаг 1: fast продвигается на k узлов вперед от головы.
  • Шаг 2: Затем slow и fast начинают двигаться синхронно, по одному узлу за шаг.
  • Когда fast достигает конца списка (fast.Next == null), slow будет указывать на нужный k-й элемент с конца.
  • Этот подход также требует O(n) времени, но выполняется одним проходом, что может быть эффективнее.

Реализация в C# (метод двух указателей)

Рассмотрим реализацию более оптимального метода с двумя указателями для односвязного списка, реализованного в виде класса.

// Определение узла односвязного списка
public class ListNode
{
    public int Value { get; set; }
    public ListNode Next { get; set; }

    public ListNode(int value)
    {
        Value = value;
        Next = null;
    }
}

// Метод для поиска k-го элемента с конца
public ListNode FindKthFromEnd(ListNode head, int k)
{
    if (head == null || k <= 0)
    {
        throw new ArgumentException("Список пуст или k имеет некорректное значение.");
    }

    ListNode fast = head;
    ListNode slow = head;

    // 1. Продвигаем быстрый указатель на k узлов вперед
    for (int i = 0; i < k; i++)
    {
        if (fast == null)
        {
            // Если список содержит меньше k элементов
            throw new ArgumentOutOfRangeException("Список содержит меньше k элементов.");
        }
        fast = fast.Next;
    }

    // 2. Синхронное движение обоих указателей
    while (fast != null)
    {
        slow = slow.Next;
        fast = fast.Next;
    }

    // slow теперь указывает на k-й элемент с конца
    return slow;
}

Обсуждение сложности и возможных проблем

  • Временная сложность: Алгоритм выполняется в O(n), где n — количество элементов в списке. Мы посещаем каждый узло один раз.
  • Память: Используется O(1) дополнительной памяти — только два указателя.
  • Пограничные случаи, которые обязательно нужно учитывать в backend-разработке:
    *   **Пустой список (`head == null`).**
    *   **Неположительное значение k (`k <= 0`).**
    *   **Список содержит меньше k элементов.** В этом случае быстрый указатель `fast` станет `null` раньше, чем продвинется на `k` шагов. Метод должен корректно обработать эту ситуацию, как показано в примере выше (выбросить исключение или вернуть `null`, исходя из требований бизнес-логики).
    *   **Если k == 1**, метод вернет последний элемент (с конца первый).
    *   **Если k равно длине списка**, метод вернет первый элемент (голову).

Почему эта задача важна для Backend разработчика?

  • Оптимизация: Backend системы часто работают с большими объемами данных. Понимание эффективных алгоритмов, даже для базовых структур, критично для написания производительного кода.
  • Обработка данных: Списки и последовательности данных (например, коллекции событий, логов, сообщений) — обычная практика. Знание как манипулировать ими эффективно — обязательный навык.
  • Качество кода: Задача проверяет внимание к деталям, обработке исключительных ситуаций и написанию чистого, поддерживаемого кода, что является основой надежного backend-сервиса.

На собеседовании, помимо реализации, стоит быть готовым обсудить альтернативные подходы, сравнить их эффективность и выбрать наиболее подходящий исходя из предполагаемого контекста использования (например, если список очень длинный и находится в памяти, метод двух указателей может быть предпочтительнее из-за одного прохода).

Как в односвязном списке найти k-й элемент с конца? | PrepBro