amarao: (Default)
[personal profile] amarao
Я тут решаю одну hard problem на leetcode (в leetcod'овском смысле 'hard', не NP-hard). Я думаю, что я её уже решил оптимально (спустя ~10 исписанных бумажек), но я не знаю как её запрограммировать на Rust. Списки и деревья - моя слабость в Rust, потому что "всё понимает, а сказать не может".

Итак, задача: https://leetcode.com/problems/maximum-segment-sum-after-removals/

Дан массив положительных чисел и массив со списком позиций для удаления чисел. Нужно заменять в первом массиве (чисел) позицию из второго списка нулём. После этого нужно смотреть на массив чисел и отвечать какая сумма сегмента там самая большая. Сегмент - непрерывная последовательность чисел без нулей. Эти суммы надо записать в результат (для каждого удаления - максимальный размер сегмента в после данного удаления).

Т.е. после каждого удаления старый сегмент может оказаться поделен на два, и возможно что какой-то другой сегмент теперь больший.

Всего для массива в N чисел будет N удалений.

Решение в лоб тривиальное - заменяем элемент в массиве, считаем какой сегмент максимальный. Сложность n², потому что у нас надо N раз посчитать максимальный сегмент среди N чисел.

Дальше спойлер



Я быстро прошёл этапы локальной оптимизации. Наверное, самый интересный был с running sum, когда нулевой элемент обнулял current running sum, но сохранял max running sum и при сканировании сравнивалось значение нуля и если оно было меньше, чем у последнего элемента, мы прыгали прямо к нему. (т.к. в прошлый прогон у нас уже в последнем элементе max был). Это всё ерунда под которую есть контр-примеры с n² сложностью, и я не уверен, что у меня бы даже и получилось что-то сэкономить (обновлять-то running max всё равно надо у всех ненулевых элементов и у некоторых нулевых..., короче, skip).

Следующая идея была хранить числа в дереве/в рекурсии стека и понимать случаи "слева были нули/не было/примыкающий ноль" и т.д. Я взорвался в дереве состояний и условий.

Наконец, решение, которое я думаю, что работает, простое:

Segments = Zero(Left: Segments, Right: Segments) ∪ Range[x..y]

Дерево нулей, в котором либо полный отрезок без нулей (точнее, значения его начала/конца), либо пара значений (лево/право от нуля), где каждое значение ссылка на ноду (дерева), то есть на структуру самого себя. Плюс кешированный max для каждой половинки, плюс None, если ноль находится с "краю".

При каждом удалении мы обходим дерево, находим сегмент, в котором находится ноль, заменяем этот сегмент на новый Zero и 0-1-2 сегмента, пересчитываем суммы для этих двух сегментов, вычисляем максимум, пересчитываем максимум у родителя (не заходя во второго потомка) и так до самого верха, верх - результат для записи в output.

Я 100% уверен, что оно работает, сложность ~o(n*log(n)) (будет вставлено n нулей, каждый ноль log (n) обходов дерева. У меня трещит голова посчитать сложность посчёта максимума для массивов, но понятно, что она резко снижается после каждого нуля. Несколько "плохих" случаев легко покрываются локальными оптимизациями (ноль с краю - мы не пересчитываем длину отрезка, а просто вычитаем из него, ноль где-то посредине - считаем до середины в меньшем интервале и принимаем решение на середине о том, чему равен остаток (мы знаем общую сумму), но понятно, что в среднем это меньше n, и оно либо стремительно сокращается с каждой итерацией (если ноль в середине), либо легко высчитывается за малое число операций, если ноль с краю. В любом случае, у нас на каждом шагу отрезается m чисел из рассмотрения... Вероятно, там есть мерзкий пример с постоянным пересчётом по большому интервалу, но мне трудно додумать туда.

В любом случае, дерево тут - путь куда надо.

Но! Деревья медленные. Надо BTree. И я совершенно не понимаю как мне использовать BTreeMap в Rust для этой задачи. Я не понимаю, что является value. Более того, я даже не понимаю, что является key, потому что в моём наивном описании дерева нода - это или ноль (его позиция и информация об нодах слева/справа), либо интервал (leaf).

Получается, мне нужно писать своё дерево? Я к этому совсем не готов, особенно для BTree.

BTreeSet выглядит интересно, мне нужно всего лишь реализовать Ord... Я могу задать Ord на Range ∪ Zero...

Грубо говоря, я гарантирую, что либо у меня ноль, либо интервал без нулей. Я делаю get на позицию нового нуля, получаю None... А мне нужно получить range, в который этот ноль прийдёт. Окей.

Ага... Мне нужно задать очень хитрое отношение с нулём.

Zero(n) === x..y if n ∈ x..y

Но при этом я про себя знаю, что они не равны, и если я получил range, мне нужно его pop, split, insert.

Да, может быть получится.
This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

Profile

amarao: (Default)
amarao

April 2026

S M T W T F S
   1234
567 891011
12131415161718
19202122232425
2627282930  

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Apr. 12th, 2026 07:15 pm
Powered by Dreamwidth Studios