Задача второго дня Advent of Code — Red-Nosed Reports.
📘 Часть I. Описание задачи
Имеется множество отчётов, каждый из которых представляет собой последовательность целых чисел — уровней. Отчёт считается безопасным, если:
- Все уровни строго возрастают или строго убывают.
- Разность между любыми двумя соседними уровнями по модулю составляет от 1 до 3 включительно.
Пример безопасных отчётов:
Пример небезопасных отчётов:
1 2 5 9 # шаг больше 3
5 4 4 1 # не строго убывает
💡 Идея
Необходимо пройтись по каждой последовательности и проверить, являются ли все пары элементов:
- либо строго возрастающими (
a < b
)
- либо строго убывающими (
a > b
)
- и при этом |a - b| <= 3
Если хотя бы одна из этих проверок не выполняется — отчёт считается небезопасным.
📘 Часть II. Описание задачи
Инженеры ввели модуль подавления проблем, который допускает одну ошибку в последовательности. То есть теперь отчёт считается безопасным, если:
- Либо он полностью безопасен по старым правилам,
- Либо из него можно удалить ровно один элемент, после чего он станет безопасным по правилам из Части I.
💡 Идея
Для каждой последовательности мы должны проверить:
- Является ли она безопасной без изменений,
- Или можно ли удалить один элемент и получить безопасную последовательность.
Удаление может быть:
- Явным (пропустить первый элемент),
- Неявным (рекурсивно рассматривать вариант с пропуском одного элемента внутри последовательности).
🛠 Подробности реализации
-
Мы создаём структуру
MonotonicChecker
, в которую инкапсулируем:
- максимально допустимый шаг (
max_step
)
- порядок сравнения (
Ordering::Less
или Ordering::Greater
)
-
Методы:
is_monotonic
: итеративно проверяет, что последовательность монотонна с допустимыми шагами
is_monotonic_with_skip
: рекурсивно проверяет, можно ли добиться монотонности, пропустив один элемент
is_valid_pair
: Проверяет, что пара чисел следует нужному порядку (<
или >
) и отличается не более чем на max_step
.
⏱ Асимптотика
-
Временная сложность:
O(k · n²)
(k
последовательностей длины ≤n
)
- Проверка последовательности на монотонность:
O(n)
- Проверка с одним пропуском:
O(n²)
__ в худшем случае,
(_так как для каждого возможного удаления элемента производится линейная проверка).
-
Пространственная сложность:
- 📌
solve_simple
— O(1)
дополнительной памяти (итератор + пара переменных).
- 📌
solve_hard
— O(n)
по стеку вызовов из-за рекурсии (is_monotonic_with_skip
).
- 🔄 Никаких дополнительных аллокаций сложных структур или временных векторов мы не делаем.
🧾 Исходный код
use super::TaskData;
use std::cmp::Ordering;
pub struct Solution;
impl Solution {
const INC_CHECKER: MonotonicChecker = MonotonicChecker {
max_step: 3,
order: Ordering::Less,
};
const DEC_CHECKER: MonotonicChecker = MonotonicChecker {
max_step: 3,
order: Ordering::Greater,
};
/// Counts strictly safe sequences: fully increasing or decreasing,
/// with adjacent elements differing by at most `max_step`.
pub fn solve_simple(data: &TaskData) -> i32 {
data.sequences
.iter()
.filter(|seq| {
Self::INC_CHECKER.is_monotonic(seq)
|| Self::DEC_CHECKER.is_monotonic(seq)
})
.count() as _
}
/// Counts sequences that are safe either directly or after skipping one element.
pub fn solve_hard(data: &TaskData) -> i32 {
data.sequences
.iter()
.filter(|seq| {
Self::INC_CHECKER.is_monotonic_with_skip(seq)
|| Self::INC_CHECKER.is_monotonic(&seq[1..])
|| Self::DEC_CHECKER.is_monotonic_with_skip(seq)
|| Self::DEC_CHECKER.is_monotonic(&seq[1..])
})
.count() as _
}
}
/// Encapsulates monotonicity check logic with a fixed max step and ordering.
#[derive(Copy, Clone)]
struct MonotonicChecker {
max_step: i32,
order: Ordering,
}
impl MonotonicChecker {
/// Checks if sequence is strictly monotonic with configured order and step.
fn is_monotonic(&self, sequence: &[i32]) -> bool {
sequence
.windows(2)
.all(|pair| self.is_valid_pair(pair[0], pair[1]))
}
/// Allows skipping at most one element to restore monotonicity.
fn is_monotonic_with_skip(&self, sequence: &[i32]) -> bool {
sequence.len() <= 2 || {
let first = sequence[0];
let second = sequence[1];
let third = sequence[2];
if self.is_valid_pair(first, second)
&& self.is_monotonic_with_skip(&sequence[1..])
{
return true;
}
self.is_valid_pair(first, third)
&& self.is_monotonic(&sequence[2..])
}
}
/// Checks if two values satisfy order and max step constraint.
fn is_valid_pair(&self, a: i32, b: i32) -> bool {
a.cmp(&b) == self.order && (a - b).abs() <= self.max_step
}
}
Tags: #rust #adventofcode #recursion