Rust на LeetCode: Решаем задачи и прокачиваем скиллы

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Rust на LeetCode: Переход от Python к системному мышлению

Если вы зашли на PythonLib, то, скорее всего, уже знакомы с Python. Вы умеете писать лаконичные решения в одну строку и используете динамическую типизацию как данность. Но мир не стоит на месте. Всё чаще на собеседованиях в топовые компании (и не только) просят написать код на Rust. Почему? Потому что Rust — это язык, который сочетает скорость C с безопасностью памяти, а LeetCode — идеальный полигон для оттачивания этого мастерства.

В этой статье мы не просто пробежимся по синтаксису. Мы разберем, как мыслить в парадигме Rust при решении алгоритмических задач, какие структуры данных использовать, и как не утонуть в borrow checker. Поехали!

Почему LeetCode на Rust — это вызов и преимущество?

Большинство решений на LeetCode написано на Python или Java. Rust же заставляет вас думать о памяти, владении и времени жизни. Это кажется сложным, но именно это и делает вас сильнее как инженера. Решая задачи на Rust, вы автоматически изучаете:

  • Систему владения (Ownership) — главную фишку языка.
  • Заимствование (Borrowing) и изменяемые/неизменяемые ссылки.
  • Эффективные итераторы без лишних аллокаций.
  • Работу с Option и Result вместо исключений.

Если вы пришли из Python, первые 10 задач покажутся вам пыткой. Но когда вы поймете, как компилятор защищает вас от null pointer и гонок данных, вы влюбитесь в этот язык.

Что нужно изучить перед стартом?

Прежде чем кидаться в пул задач, давайте разберем инструментарий. Без него вы будете писать код как на C++ с макросами — неэффективно и больно.

1. Итераторы и замыкания (Closures)

В Rust итераторы — это не просто цикл for. Это мощный функциональный инструмент. Вместо того чтобы писать for i in 0..vec.len(), учитесь использовать .iter(), .map(), .filter(), .fold().

// Python: [x*2 for x in list if x > 0]
// Rust:
let result: Vec<i32> = list.iter()
    .filter(|&x| *x > 0)
    .map(|x| x * 2)
    .collect();

Этот код не только короче, но и работает быстрее, так как итераторы ленивы и компилятор оптимизирует их в линейный цикл.

2. HashMap и BTreeMap

Для задач на подсчёт частот (например, "Two Sum" или "Group Anagrams") HashMap — ваш лучший друг. Но помните: в Rust вставка в HashMap требует владения ключом. Если вы используете &str, вам придётся клонировать строку или использовать String.

use std::collections::HashMap;

fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
    let mut map = HashMap::new();
    for (i, &num) in nums.iter().enumerate() {
        let complement = target - num;
        if let Some(&j) = map.get(&complement) {
            return vec![j as i32, i as i32];
        }
        map.insert(num, i);
    }
    vec![]
}

Обратите внимание: enumerate() возвращает кортеж (usize, &i32). Мы используем &num, чтобы разыменовать ссылку.

3. Vec и слайсы

Vec — это динамический массив. Но в задачах LeetCode часто нужно работать с подмассивами. Для этого используйте слайсы &[T]. Они не требуют копирования и позволяют передавать часть массива в функцию.

fn max_subarray_sum(arr: &[i32]) -> i32 {
    let mut max_ending = arr[0];
    let mut max_so_far = arr[0];
    for &x in arr[1..].iter() {
        max_ending = std::cmp::max(x, max_ending + x);
        max_so_far = std::cmp::max(max_so_far, max_ending);
    }
    max_so_far
}

Топ-5 типичных ошибок новичков в Rust на LeetCode

Даже опытные Python-разработчики совершают одни и те же промахи. Давайте разберем их, чтобы вы не наступали на те же грабли.

Ошибка 1: Игнорирование владения при возврате из функции

Вы написали функцию, которая принимает &str и возвращает &str. Компилятор ругается на время жизни. Решение: либо возвращайте String, либо используйте аннотации времени жизни 'a.

// Неправильно (если строки разные):
fn longest(x: &str, y: &str) -> &str { // Ошибка!
    if x.len() > y.len() { x } else { y }
}

// Правильно:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

Ошибка 2: Паника при доступе к Option

В Python вы привыкли к dict.get(key), который возвращает None. В Rust HashMap::get() возвращает Option<&V>. Не используйте .unwrap() бездумно — это приведёт к панике на пустом значении. Используйте if let или match.

if let Some(value) = map.get(&key) {
    // работаем с value
} else {
    // ключа нет
}

Ошибка 3: Изменяемое и неизменяемое заимствование одновременно

Классика: вы проходите по вектору в цикле и пытаетесь его модифицировать. Компилятор скажет: "Нельзя". Используйте iter_mut() или индексы.

let mut vec = vec![1, 2, 3];
for i in 0..vec.len() {
    vec[i] += 1; // Так можно
}
// for x in &vec { vec.push(1); } // Так нельзя!

Ошибка 4: Сложности с матрицами (Vec<Vec<i32>>)

Двумерные массивы в Rust — это боль. Обращение по индексу matrix[i][j] работает, но если вы хотите изменить строку, вам нужно получить &mut Vec<i32>.

fn rotate(matrix: &mut Vec<Vec<i32>>) {
    let n = matrix.len();
    for i in 0..n {
        for j in i..n {
            let temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix                

Блоги

Рекомендаций по книгам