Честный разбор с решением, которое не стыдно показать на собеседовании, я всё же сделаю :)
Идея 💡
Задача состоит в том, чтобы разделить массив на максимальное количество блоков, так чтобы сортировка каждого блока и их объединение давали отсортированный массив. Ключевая идея заключается в том, что элементы из одного цикла перестановки
arr
1 → arr
2 → arr
3 → ... → arr
n → arr
1
обязательно должны находиться в одном блоке, так как они взаимозависимы.
Описание подхода 🛠️
- Создаём массив
chunk_assignment
, чтобы отслеживать принадлежность каждого индекса к определённому блоку.
- Используем функцию
assign_chunk
для рекурсивного назначения всех индексов, связанных с текущим индексом, в один блок. Она:
- Обновляет максимальный индекс текущего блока.
- Помечает все посещённые индексы текущим номером блока.
- Внешний цикл:
- Перебирает индексы массива.
- Увеличивает счётчик блоков при обнаружении нового блока.
- Завершает обработку текущего блока, когда все его элементы покрыты.
- Возвращаем общее количество блоков
Асимптотика 📊
- Время:
O(n)
, так как каждый элемент обрабатывается один раз.
- Память:
O(n)
, для хранения массива chunk_assignment
.
Исходный код решения 📜
impl Solution {
pub fn max_chunks_to_sorted(arr: Vec<i32>) -> i32 {
let n = arr.len();
let mut chunk_assignment = vec![0; n]; // Tracks which chunk each index belongs to
let mut chunk_count = 0; // Counter for the number of chunks
// Helper function to assign indices to a chunk
let mut assign_chunk = |start_idx: usize, chunk: i32, max_idx: &mut usize| {
let mut current_idx = start_idx;
while chunk_assignment[current_idx] == 0 {
*max_idx = (*max_idx).max(current_idx);
chunk_assignment[current_idx] = chunk;
current_idx = arr[current_idx] as usize;
}
};
let mut start_idx = 0;
while start_idx < n {
chunk_count += 1; // Start a new chunk
let mut max_idx = start_idx;
// Process the current chunk until all related indices are covered
loop {
assign_chunk(start_idx, chunk_count, &mut max_idx);
if start_idx == max_idx {
break; // The current chunk is complete
}
start_idx += 1; // Move to the next index
}
start_idx += 1; // Move to the next unprocessed index
}
chunk_count // Return the total number of chunks
}
}
перейти
Ещё один простой вариант решения - собрать все слайсы, отвечающие словам в одну коллекцию и проджойнить стандартными средствами через пробел.
impl Solution {
pub fn add_spaces(s: String, spaces: Vec<i32>) -> String {
// Convert indices to usize
let mut spaces: Vec<usize> = spaces.into_iter().map(|s_pos| s_pos as usize).collect();
// Add the start (0) and end (s.len()) boundaries to spaces
spaces.insert(0, 0);
spaces.push(s.len());
// Create slices from spaces indices
let slices: Vec<&str> = spaces.windows(2)
.map(|window| &s[window[0]..window[1]])
.collect();
// Join the slices with spaces
slices.join(" ")
}
}
В этом коде есть один грустный момент - необходимость создания промежуточного вектора из слайсов исходной строки.
К сожалению, join
из стандартной библиотеки позволяет только такое использование.
Но, если мы не ограничеы стандартной библиотекой, то крейт itertools спешит нам на помощь ;)
перейти
Кстати, используя кучу 3 максимумов, можно упростить код и выкинуть отдельный метод get_triple_length(...)
.
Достаточно просто с каждый найденным сегментом длины k
одновременно в кучу добавлять k-1
и k-2
.
Тогда наименьший элемент в куче после всех операций и будет ответом.
// Function to find the maximum special substring length for a given character.
fn solve_char(data: &[u8], ch: u8) -> i32 {
let mut segments = BinaryHeap::new();
let mut i = 0;
while i < data.len() {
if data[i] == ch {
let mut length = 1;
// Count consecutive characters matching `ch`
while i + length < data.len() && data[i + length] == ch {
length += 1;
}
// Push adjusted lengths into the heap
for k in 0..3 {
if length <= k {break}
if segments.len() >= 3 && Reverse(length - k) >= *segments.peek().unwrap() {break}
segments.push(Reverse(length - k));
}
// Maintain only the top 3 lengths
while segments.len() > 3 {
segments.pop();
}
i += length; // Skip the processed segment
}
i += 1;
}
// Return the third-largest value if we have 3 elements, otherwise -1
if segments.len() == 3 {
match segments.pop().unwrap() {
Reverse(l) => l as i32,
}
} else {
-1
}
}
перейти
Кажется что мы разговариваем на сильно разных английских.
По моему опыту:
- slayer вышел из употребления, последние пару раз я видел это слово в названии группы
- triggerman, manslayer вообще ни разу в жизни не встречал
- cutthroat до сегодняшнего дня был уверен что это исключительно прилагательное
- insecurity имхо к ревности имеет крайне опосредованое отношение, оно скорее про низкую самооценку и недостаток признания
- intelligentsia видел только применительно к СССР, в дискуссиях о США это скорее cultural elite/educated class/chattering class
перейти
Я освободил свой разум и понял первое решение. По-моему, его не стыдно и на собесе написать (разве что не в таком стиле, а циклом), оно не эзотерическое.
перейти
Интересно, в каких компаниях такие ужасы спрашивают. У меня платного leetcode нет, не подскажешь, если у тебя есть, конечно?
перейти
А в Rust нет аналога std::nth_element? Можно было бы от klogk избавиться (не то чтобы это на практике очень важно, конечно)
перейти
- slayer книжное слово, и, мне кажется, ничем в этом смысле не отличается от "душегуба" и "головореза". В GoT кличку Kingslayer не снабжали сноской и не поясняли
- я не знаю, как перевести "он ревнует свою жену ко всем подряд" с сохранением смысла без использования слов insecurity/insecure. Со словом jealousy/jealous получается совсем другое утверждение, что-то вроде "он (думает, что) не получает от своей жены того, что достается всем подряд, и от этого страдает"
- intelligentsia в мемах пример 1, пример 2. Это я сейчас поиском нашел, но вообще на сабстеке периодически натыкался в живой природе
перейти
Выглядит как хайп ради хайпа. Также как было с микро черными дырами от Большого Андронного Коллайдера.
То, что бактерию никто не сможет съесть вообще не означает, что она будет размножаться свободно. Как минимум конкурентная борьба за ресурсы с более приспособленными к борьбе обычными бактериями её сильно ограничит.
Ну и касательно ресурсов. Такая бактерия, как я понимаю, скорее всего будет хемотрофной, а вот для современных хемотрофов ниши весьма специфичны - ни разу не серая слизь по описанию.
перейти
Даже в этом решении, если заглянуть глубже split_whitespace
не массив слайсов возвращает, а итератор по ним
sruct.SplitWhitespace. В спецификации его характеристики по дополнительной памяти не уточняются, но вполне возможно реализовать такой итератор на константных костах. И можно обсудить как сделать итератор с такими гарантиями самостоятельно.
перейти
Согласен, аналогичная история.
Мое доказательство основано на тождестве 0-0=0. Допустим, наше решение не оптимально. Рассмотрим первый несовпадающий чанк в нашем решении и в оптимальном. Оба имеют нулевой баланс. В нашем решении он короче, потому что иначе мы сформировали бы его раньше. Тогда чанк в якобы оптимальном решении можно разбить на два -- наш и остаток. Пришли к противоречию.
перейти
Ненавижу такие задачи. Алгоритмически она тривиальная, но, конечно же, в условиях таймпрессинга я с какой-то вероятностью забуду про спецслучай нуля. Это ничего не говорит не только о моем умении программировать, но даже о моем умении решать литкод. Да, в жизни тоже бывают спецслучаи, про которые нельзя забывать, и это часть работы. Но не такие!
перейти
Ты прав, так можно!
В Rust - этот метод называется немного по уродски, но есть: select_nth_unstable.
Есть и проще метод - нужно просто 3 итерации BubbleSort'a прогнать и они гарантируют 3 наибольших значения на своих местах.
перейти
Достаточно просто показать, что при увеличении числа студентов в классе margin gain
только падает. А это в свою очередь означает, что любая цепочка добавлений студентов в разные классы может быть отранжирована по убыванию margin gain
и корректно применена уже в новом порядке.
перейти
Для максимизации среднего соотношения нужно жадно добавлять студентов
Почему? Это совершенно не очевидное математически утверждение. У нас тут какая-то очень ядреная функция от n переменных, почему жадным методом мы придем в её глобальный минимум, а не в локальный?
перейти
В реальном собеседовании поверх такой задачи будет доп.требование "...без выделения доп.памяти", тогда у нее появляется понятный смысл -- проверить умение человека не запутаться в двух индексах и краевых условиях.
перейти
Хм, здесь появилась жизнь! Сертификат обновил =)
перейти
С БАК было более очевидно -- емнип был простой контраргумент, состоящий в том, что энергия столкновения все еще меньше энергии некоторых (редких, но время от времени вполне регистрируемых) космических частиц, входящих в атмосферу Земли. Т.е. аналогичные события в природе происходят "сами собой", нет ничего особенного конкретно в БАК.
В случае с бактерией неправильной хиральности есть небессмысленная модель угроз: в биосфере появляется незамкнутая цепочка переработки (бактерия вряд ли сможет замкнуть все циклы в одиночку). Подобное в реальной истории жизни на Земле уже несколько раз происходило, например, carboniferous period. В результате происходит накопление неиспользуемых веществ и вывод их из активного оборота до тех пор, пока не эволюция не произведет кого-то, кто умеет их перерабатывать, что может занять десятки миллионов лет. Для этого не обязательно, чтобы новая бактерия была "суперхищником", достаточно, чтобы она просто быстро не сдохла.
Ну и в целом соотношения risk-reward тут крайне сомнительные.
перейти
А, и правда. Я не пишу на Rust и не знал, что там split возвращает итератор.
Но я больше не про память, а про то, что в текущей постановке задача почти что fizzbuzz: проверяет, что кандидат знает синтаксис языка и то, как в нем обращаются со строками в стандартной библиотеке. "Дальше можно обсудить" это понятно, но если в процессе такого обсуждения код не писать, то проверка, что человек не путается в индексах и знает, что делать со строчками, заканчивающимися на пробел, не состоится. Имхо лучше, если задача сразу так поставлена, чтобы она возникала естественным образом.
перейти
А, я понял.
То есть речь о рисках небыстрого, но полного занятия какой-то биоподобной ниши.
С таким я согласен, возможно.
Но всё же, я также и почти уверен, что заниматься эта ниша будет даже не годами, а тысячелетиями или десятками тысяч лет. Это совсем копейки с точки зрения геологической летописи, но совсем иной уровень угроз, с точки зрения человеческой цивилизации (мы тупо парниковыми газами успеем раньше всё загадить).
перейти
Мне на самом деле практика именно задач через LeetCode не очень нравится тем, что тесты уже даются системой.
Интересная часть, в которой кандидат сам придумывает крайние случаи для своего кода исключаются.
Я понимаю, с другой стороны, что это идёт в угоду сокрашения времени. Меж тем это всё-равно самая важная часть отладки на мой взгляд, позволяющая отсечь тех кто программирует сам, от использующих ChatGPT.
перейти
В первом решении - самое сложно на мой взгляд - доказать корректность.
Не то, чтобы это было почти невозможно, возможно и за разумное время.
Но на доказательство, почему он работает (и в том числе безуспешные попытки найти контрпример) у меня ушло больше времени, чем на написание/понимание кода.
перейти