---
title: Peeking Iterator
difficulty: Medium
category: Iterator
leetcode_id: 284
revision_count: 1
last_revision: 2026-09-12
---

# Peeking Iterator

> **LeetCode:** [#284](https://leetcode.com/problems/peeking-iterator/)
> **Сложность:** Medium
> **Категория:** Iterator

## Условие

Дан стандартный интерфейс `Iterator` с методами `next()` и `hasNext()`. Реализовать класс `PeekingIterator`, который добавляет операцию `peek()` — посмотреть следующий элемент без продвижения итератора.

```
peeking_iterator = PeekingIterator.new(Iterator.new([1, 2, 3]))
peeking_iterator.next()    → 1
peeking_iterator.peek()    → 2  # не продвигает итератор
peeking_iterator.next()    → 2
peeking_iterator.hasNext() → true
peeking_iterator.next()    → 3
peeking_iterator.hasNext() → false
```

## Ключевая идея

Кешируем (буферизируем) следующий элемент. `peek()` сохраняет значение, полученное через `next()` родителя, и возвращает его. `next()` отдаёт закешированное значение, если оно есть, иначе делегирует родителю. `hasNext()` проверяет кеш или родительский итератор.

## Оценка сложности

- **Время:** O(1) на каждую операцию
- **Память:** O(1) — храним только одно закешированное значение + флаг

## Решение (Ruby)

```ruby
# @param {Iterator} iterator — родительский итератор с методами next() и hasNext()
class PeekingIterator
  def initialize(iterator)
    @it = iterator
    @cached = nil          # nil = кеш пуст; иначе хранит значение
    @has_cached = false
  end

  # Возвращает следующий элемент без продвижения итератора
  def peek
    if !@has_cached
      @cached = @it.next
      @has_cached = true
    end
    @cached
  end

  # Возвращает следующий элемент и продвигает итератор
  def next
    if @has_cached
      @has_cached = false
      @cached           # возвращаем, потом сбрасываем — порядок не важен
    else
      @it.next
    end
  end

  # Возвращает true, если есть ещё элементы
  def has_next
    if @has_cached
      true
    else
      @it.has_next
    end
  end
end
```

## Разбор на примере

```
Iterator values: [1, 2, 3]

Состояние: @cached = nil, @has_cached = false

peek():
  @has_cached = false → берем @it.next = 1, кешируем
  возвращаем 1

next():
  @has_cached = true → сбрасываем флаг, возвращаем @cached (1)
  состояние: @cached = 1, @has_cached = false

peek():
  @has_cached = false → берем @it.next = 2, кешируем
  возвращаем 2

next():
  @has_cached = true → сбрасываем флаг, возвращаем 2

next():
  @has_cached = false → делегируем @it.next = 3

has_next():
  @has_cached = false → делегируем @it.has_next = false
```

## Edge Cases

- **Один элемент**: `Iterator.new([42])` — `peek()` возвращает 42, `next()` возвращает 42, `has_next()` → false.
- **Пустой итератор**: `Iterator.new([])` — `has_next()` → false, `peek()` упадёт (делегирует `@it.next`). В условии задачи гарантируется, что `peek`/`next` вызывают только если `has_next` true.
- **Несколько `peek()` подряд**: первый `peek()` кеширует значение, последующие возвращают то же самое — итератор не продвигается.
- **Чередование `peek()` и `next()`**: кеш корректно синхронизируется флагом `@has_cached`.

## Типичные ошибки

1. ❌ **Дважды вызвать `@it.next` при `peek()`** — если кеш уже заполнен, повторный вызов продвинет итератор и потеряет элемент. Флаг `@has_cached` защищает от этого.
2. ❌ **Забыть сбросить `@cached` в `nil` после `next()`** — не критично, значение перезапишется при следующем `peek()`, но может сбивать с толку при отладке.
3. ❌ **Использовать `nil` как маркер "нет элемента"** — если в итераторе есть `nil` как допустимое значение, это сломает логику. Ruby не гарантирует отсутствие nil в данных (в отличие от Java с `Objects.requireNonNull`). Флаг `@has_cached` явно решает эту проблему.

## Альтернативные подходы

| Подход | Время | Память | Комментарий |
|--------|-------|--------|-------------|
| **Cache-флаг + буфер** | **O(1)** | **O(1)** | **финальное решение** |
| Вызов `next()` при каждом `peek()` с сохранением истории | O(n) | O(n) | Не нужно: храним весь пройденный путь |

Единственный разумный подход — с буфером на один элемент. Альтернативы избыточны.

## Заметки после повторения

_— Заполняется после каждой тренировки._