- PVSM.RU - https://www.pvsm.ru -

Как решать LeetCode? Легко! Нужно просто…

Просто знать 15 важных паттернов, которые помогут облегчить тернистый путь в решении алгоритмических задач. Про эти паттерны мы и расскажем в этой статье.

Перевод статьи автора Ashish Pratap Singh. Авторы перевода: Java-разработчики Никита С. и Антон П.

Перевод статьи автора Ashish Pratap Singh. Авторы перевода: Java-разработчики Никита С. и Антон П.

На сегодняшний день алгоритмические задачи встречаются не только в FAANG. Многие компании и на отечественном рынке всё чаще вводят дополнительный алгоритмический этап на собеседовании – и знание алгоритмов становится отличным «плюсиком» не только при трудоустройстве, но и в решении повседневных задач. Взглянем подробнее на эти паттерны.


Прорешав более 1500 задач в LeetCode, я пришёл в такому выводу:

LeetCode - это не столько о том, сколько задач ты решил, сколько о том, сколько паттернов ты знаешь.

Знание паттернов поможет вам решать больше задач за меньшее время и быстрее определить подход к решению задачи, с которой вы раньше не сталкивались.

В этой статье мы разберём 15 видов паттернов, которые сделали мой опыт в LeetCode менее болезненным. Я подробно разберу каждый из них и оставлю ссылки на задачи в LeetCode, чтобы вы могли попрактиковаться.

1. Префиксные суммы (Prefix Sum)

Паттерн 1: Префиксные суммы

Паттерн 1: Префиксные суммы

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

Когда использовать: в ситуации, когда нужно выполнить несколько запросов сумм к подмассиву или вычислить совокупные суммы.

Пример задачи:

Дан массив nums. Вычислите сумму элементов в диапазоне [i, j].

Пример:

  • Вход: nums = [1, 2, 3, 4, 5, 6], i = 1, j = 3

  • Выход: 9

Объяснение:

  • Создаём префиксный массив: P = [1, 3, 6, 10, 15, 21]

  • Сумма элементов будет равна [i, j] = P[j] - P[i-1] (при i > 0)

Применимо к задачам:

Пример решения на Java: ссылка [4]

2. Два указателя (Two pointers)

Паттерн 2: Два указателя

Паттерн 2: Два указателя

Паттерн двух указателей использует два указателя для обхода массива или списка. Часто применяется для поиска пар элементов, удовлетворяющих определённому условию.

Когда использовать: при работе с отсортированными массивами или списками, где нужно найти пары, удовлетворяющие условию (например, сумма = target).

Пример задачи:

Найти два числа в отсортированном массиве, сумма которых равна заданному значению.

Пример:

  • Вход: nums = [1, 2, 3, 4, 6], target = 6

  • Выход: [1, 3] (индексы)

Объяснение:

  • Инициализируем указатели: left = 0, right = len(nums) – 1

  • Проверяем сумму элементов по двум указателям

  • Если nums[left] + nums[right] == target → нашли решение

  • Если сумма меньше — двигаем left вправо

  • Если сумма больше — двигаем right влево

Применимо к задачам:

Пример решения на Java: ссылка [8]

3. Скользящее окно (Sliding Window)

Паттерн 3: Скользящее окно

Паттерн 3: Скользящее окно

Шаблон скользящего окна используется для поиска подмассива или подстроки, удовлетворяющей определённому условию. Он оптимизирует временную сложность за счёт поддержания «окна» фиксированного или переменного размера.

Когда использовать: при работе с непрерывными подмассивами или подстроками.

Пример задачи:

Найти максимальную сумму подмассива размера k.

Пример:

  • Вход: nums = [2, 1, 5, 1, 3, 2], k = 3

  • Выход: 9

Объяснение:

  • Считаем сумму первых k элементов

  • Затем «скользим» окно: вычитаем уходящий элемент, прибавляем входящий

  • Отслеживаем максимальную сумму

Применимо к задачам:

Пример решения на Java: ссылка [12]

4. Быстрый и медленный указатели (Fast & Slow Pointers)

Паттерн 4: Быстрый и медленный указатели

Паттерн 4: Быстрый и медленный указатели

Этот шаблон (известен как «черепаха и заяц») используется для обнаружения циклов в связных списках и других структурах.

Когда использовать: когда нужно определить наличие цикла, найти середину списка или решить задачи на цикличность.

Пример задачи:

Определить, содержит ли связный список цикличность.

Объяснение:

  • Инициализируем два указателя: slow (1 шаг за итерацию), fast (2 шага)

  • Если fast и slow встречаются — цикличность есть

  • Если fast дошёл до конца — цикличности нет

Применимо к задачам:

Пример решения на Java: ссылка [16]

5. Разворот связного списка на месте (In-place Reversal of LinkedList)

Паттерн 5: Разворот связного списка на месте

Паттерн 5: Разворот связного списка на месте

Этот шаблон позволяет разворачивать часть связного списка без дополнительного пространства.

Когда использовать: когда нужно развернуть подсписок или весь список «на месте».

Пример задачи:

Развернуть подсписок с позиции m по n.

Пример:

  • Вход: head = [1, 2, 3, 4, 5], m = 2, n = 4

  • Выход: [1, 4, 3, 2, 5]

Объяснение:

  • Находим узлы до m и после n

  • Разворачиваем участок между ними, корректируя указатели

Применимо к задачам:

Пример решения на Java: ссылка [20]

6. Монотонный стек (Monotonic Stack)

Паттерн 6: Монотонный стек

Паттерн 6: Монотонный стек

Монотонный стек поддерживает элементы в стеке в строго возрастающем или убывающем порядке. Часто используется для поиска следующего большего/меньшего элемента.

Когда использовать: когда нужно найти ближайший больший/меньший элемент слева или справа.

Пример задачи:

Для каждого элемента массива найти следующий больший элемент. Если такого нет — вывести -1.

Пример:

  • Вход: nums = [2, 1, 2, 4, 3]

  • Выход: [4, 2, 4, -1, -1]

Объяснение:

  • Используем стек чтобы отслеживать элементы, для которых мы еще не нашли следующий больший элемент

  • Перебираем массив и извлекаем элементы из стека для каждого элемента, пока не найдем больший элемент

  • Если стек не пуст, устанавливаем результат для индекса наверху стека в текущий элемент

  • Помещаем текущий элемент в стек

Применимо к задачам:

Пример решения на Java: ссылка [24]

7. Топ K элементов (Top ‘K’ Elements)

Паттерн 7: Топ К элементов

Паттерн 7: Топ К элементов

Этот шаблон находит K наибольших или наименьших элементов с помощью куч (heap) или сортировки.

Когда использовать: при поиске медианы, топ-K элементов в потоке.

Пример задачи:

Найти K-й по величине элемент в неотсортированном массиве.

Пример:

  • Вход: nums = [3, 2, 1, 5, 6, 4], k = 2

  • Выход: 5

Объяснение:

  • Используем минимальную кучу (min-heap) размера k, чтобы отслеживать k крупнейших элементов

  • Перебираем массив, добавляя элементы в кучу

  • Если размер кучи превышает k, удаляем из кучи наименьший элемент

  • Корнем кучи будет k-й по величине элемент

Применимо к задачам:

Пример решения на Java: ссылка [28]

8. Перекрывающиеся интервалы (Overlapping Intervals)

Паттерн 8: Перекрывающиеся интервалы

Паттерн 8: Перекрывающиеся интервалы

Шаблон для слияния или обработки интервалов, которые пересекаются.

Когда использовать: при работе с графиками, расписаниями, объединением диапазонов.

Пример задачи:

Слить все перекрывающиеся интервалы.

Пример:

  • Вход: [[1,3], [2,6], [8,10], [15,18]]

  • Выход: [[1,6], [8,10], [15,18]]

Объяснение:

  • Сортируем интервалы по началу

  • Создаем пустой список с именем merged для хранения объединенных интервалов

  • Перебираем интервалы и проверяем, не перекрывается ли они с последним интервалом в объединенном списке

  • Если они перекрываются, объединяем интервалы, обновив время окончания последнего объединенного интервала

  • Если они не пересекаются, просто добавляем текущий интервал в объединенный список

Применимо к задачам:

Пример решения на Java: ссылка [32]

9. Модифицированный бинарный поиск (Modified Binary Search)

Паттерн 9: Модифицированный бинарный поиск

Паттерн 9: Модифицированный бинарный поиск

Адаптация бинарного поиска для вращённых отсортированных массивов, нахождения границ и т.д.

Когда использовать: при работе с отсортированными или повёрнутыми массивами и поиске элементов.

Пример задачи:

Найти элемент в повёрнутом отсортированном массиве.

Пример:

  • Вход: nums = [4,5,6,7,0,1,2], target = 0

  • Выход: 4 (индекс)

Объяснение:

  • Выполняем двоичный поиск с дополнительной проверкой, чтобы определить, какая половина массива отсортирована

  • Затем проверяем, находится ли цель в пределах отсортированной половины

  • Если да, ищем эту половину; в противном случае ищем вторую половину

Применимо к задачам:

Пример решения на Java: ссылка [36]

10. Обход бинарного дерева (Binary Tree Traversal)

Паттерн 10: Обход бинарного дерева

Паттерн 10: Обход бинарного дерева

Обход всех узлов дерева в определённом порядке:

  • PreOrder: корень → лево → право

  • InOrder: лево → корень → право

  • PostOrder: лево → право → корень

Когда использовать: почти во всех задачах на деревья.

Пример задачи:

Выполнить inorder-обход.

Пример:

  • Вход: root = [1, null, 2, 3]

  • Выход: [1, 3, 2]

Объяснение:

  • При неупорядоченном обходе узлы посещаются в следующем порядке: левый, корневой, правый.

  • Используем рекурсию или стек для обхода дерева в этом порядке.

Применимо к задачам:

Пример решения на Java: ссылка [32]

11. Поиск в глубину (DFS)

Паттерн 11: Поиск в глубину

Паттерн 11: Поиск в глубину

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

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

Пример задачи:

Найти все пути от корня до листьев.

Пример:

  • Вход: root = [1,2,3,null,5]

  • Выход: ["1->2->5", "1->3"]

Объяснение:

  • Используем рекурсию или стек для прохождения каждого пути от корня к листьям.

  • Записываем каждый путь по мере прохождения.

Применимо к задачам:

Пример решения на Java: ссылка [43]

12. Поиск в ширину (BFS)

Паттерн 12: Поиск в ширину

Паттерн 12: Поиск в ширину

Узлы исследуются уровень за уровнем в дереве или графе.

Когда использовать: для поиска кратчайшего пути в невзвешенном графе, обхода по уровням.

Пример задачи:

Выполнить уровневый обход бинарного дерева.

Пример:

  • Вход: root = [3,9,20,null,null,15,7]

  • Выход: [[3], [9,20], [15,7]]

Объяснение:

  • Используем очередь для отслеживания узлов на каждом уровне.

  • Проходим каждый уровень и добавляем в очередь дочерние элементы текущих узлов.

Применимо к задачам:

Пример решения на Java: ссылка [47]

13. Обход матрицы (Matrix Traversal)

Паттерн 13: Обход матрицы

Паттерн 13: Обход матрицы

Обход элементов матрицы с помощью DFS, BFS и других техник.

Когда использовать: при работе с двумерной сеткой (grid) или матрицой, островами, заливкой и т.п.

Пример задачи:

Выполнить заливку (flood fill) — изменить цвет всех смежных клеток, начиная с заданной.

Пример:

  • Вход: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, newColor = 2

  • Выход: [[2,2,2],[2,2,0],[2,0,1]]

Объяснение:

  • Используем DFS или BFS для перемещения по матрице, начиная с заданной ячейки.

  • Изменяем цвет соединенных ячеек на новый цвет.

Применимо к задачам:

Пример решения на Java: ссылка [51]

14. Возврат (Backtracking)

Паттерн 14: Возврат

Паттерн 14: Возврат

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

Когда использовать: для задач на перестановки, комбинации, размещения, судоку и т.д.

Пример задачи:

Сгенерировать все перестановки списка.

Пример:

  • Вход: nums = [1,2,3]

  • Выход: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

Объяснение:

  • Используем рекурсию для создания перестановок.

  • Для каждого элемента включаем его в текущую перестановку и рекурсивно сгенерируем оставшиеся перестановки.

  • Возвращаемся назад, когда сгенерированы все перестановки для данного пути.

Применимо к задачам:

Пример решения на Java: ссылка [55]

15. Динамическое программирование (Dynamic Programming)

Паттерн 15: Динамическое программирование

Паттерн 15: Динамическое программирование

ДП разбивает задачу на подзадачи и решает их, избегая повторных вычислений.

Когда использовать: когда есть перекрывающиеся подзадачи и оптимальная подструктура.

Подтипы ДП:

  • Числа Фибоначчи

  • Рюкзак (0/1 Knapsack)

  • Наибольшая общая подпоследовательность (LCS)

  • Наибольшая возрастающая подпоследовательность (LIS)

  • Сумма подмножества

  • Умножение цепочки матриц

Пример задачи:

Найти n-е число Фибоначчи.

Пример:

  • Вход: n = 5

  • Выход: 5 (последовательность: 0, 1, 1, 2, 3, 5)

Объяснение:

  • Используем восходящий подход для вычисления n-го числа Фибоначчи.

  • Начинаем с первых двух чисел (0 и 1) и выполняем итерацию для вычисления следующих чисел, например (dp[i] = dp[i - 1] + dp[i - 2]).

Применимо к задачам:

Пример решения на Java: ссылка [62]

Все решения в нашем GitLab: ссылка [63]

Автор: MaDeLa

Источник [64]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/java/435991

Ссылки в тексте:

[1] Range Sum Query - Immutable (LeetCode #303): https://leetcode.com/problems/range-sum-query-immutable/description/

[2] Contiguous Array (LeetCode #525): https://leetcode.com/problems/contiguous-array/description/

[3] Subarray Sum Equals K (LeetCode #560): https://leetcode.com/problems/subarray-sum-equals-k/description/

[4] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/PrefixSum.java

[5] Two Sum II - Input Array is Sorted (LeetCode #167): https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/

[6] 3Sum (LeetCode #15): https://leetcode.com/problems/3sum/description/

[7] Container With Most Water (LeetCode #11): https://leetcode.com/problems/container-with-most-water/description/

[8] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/TwoPointers.java

[9] Maximum Average Subarray I (LeetCode #643): https://leetcode.com/problems/maximum-average-subarray-i/description/

[10] Longest Substring Without Repeating Characters (LeetCode #3): https://leetcode.com/problems/longest-substring-without-repeating-characters/description/

[11] Minimum Window Substring (LeetCode #76): https://leetcode.com/problems/minimum-window-substring/description/

[12] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/SlidingWindow.java

[13] Linked List Cycle (LeetCode #141): https://leetcode.com/problems/linked-list-cycle/description/

[14] Happy Number (LeetCode #202): https://leetcode.com/problems/happy-number/description/

[15] Find the Duplicate Number (LeetCode #287): https://leetcode.com/problems/find-the-duplicate-number/description/

[16] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/LinkedListCycle.java

[17] Reverse Linked List (LeetCode #206): https://leetcode.com/problems/reverse-linked-list/description/

[18] Reverse Linked List II (LeetCode #92): https://leetcode.com/problems/reverse-linked-list-ii/description/

[19] Swap Nodes in Pairs (LeetCode #24): https://leetcode.com/problems/swap-nodes-in-pairs/description/

[20] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/ReverseSublist.java

[21] Next Greater Element I (LeetCode #496): https://leetcode.com/problems/next-greater-element-i/description/

[22] Daily Temperatures (LeetCode #739): https://leetcode.com/problems/daily-temperatures/description/

[23] Largest Rectangle in Histogram (LeetCode #84): https://leetcode.com/problems/largest-rectangle-in-histogram/description/

[24] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/MonotonicStack.java

[25] Kth Largest Element in an Array (LeetCode #215): https://leetcode.com/problems/kth-largest-element-in-an-array/description/

[26] Top K Frequent Elements (LeetCode #347): https://leetcode.com/problems/top-k-frequent-elements/description/

[27] Find K Pairs with Smallest Sums (LeetCode #373): https://leetcode.com/problems/find-k-pairs-with-smallest-sums/description/

[28] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/TopKElements.java

[29] Merge Intervals (LeetCode #56): https://leetcode.com/problems/merge-intervals/description/

[30] Insert Interval (LeetCode #57): https://leetcode.com/problems/insert-interval/description/

[31] Non-Overlapping Intervals (LeetCode #435): https://leetcode.com/problems/non-overlapping-intervals/description/

[32] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/BinaryTreeTraversal.java

[33] Search in Rotated Sorted Array (LeetCode #33): https://leetcode.com/problems/search-in-rotated-sorted-array/description/

[34] Find Minimum in Rotated Sorted Array (LeetCode #153): https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/

[35] Search a 2D Matrix II (LeetCode #240): https://leetcode.com/problems/search-a-2d-matrix-ii/description/

[36] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/RotatedBinarySearch.java

[37] Binary Tree Paths (LeetCode #257): https://leetcode.com/problems/binary-tree-paths/description/

[38] Kth Smallest Element in a BST (LeetCode #230): https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/

[39] Binary Tree Maximum Path Sum (LeetCode #124): https://leetcode.com/problems/binary-tree-maximum-path-sum/description/

[40] Clone Graph (LeetCode #133): https://leetcode.com/problems/clone-graph/description/

[41] Path Sum II (LeetCode #113): https://leetcode.com/problems/path-sum-ii/description/

[42] Course Schedule II (LeetCode #210): https://leetcode.com/problems/course-schedule-ii/description/

[43] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/DFSRootToLeaves.java

[44] Binary Tree Level Order Traversal (LeetCode #102): https://leetcode.com/problems/binary-tree-level-order-traversal/description/

[45] Rotting Oranges (LeetCode #994): https://leetcode.com/problems/rotting-oranges/description/

[46] Word Ladder (LeetCode #127): https://leetcode.com/problems/word-ladder/description/

[47] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/BFSLevelOrder.java

[48] Flood Fill (LeetCode #733): https://leetcode.com/problems/flood-fill/description/

[49] Number of Islands (LeetCode #200): https://leetcode.com/problems/number-of-islands/description/

[50] Surrounded Regions (LeetCode #130): https://leetcode.com/problems/surrounded-regions/description/

[51] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/FloodFillDFS.java

[52] Permutations (LeetCode #46): https://leetcode.com/problems/permutations/description/

[53] Subsets (LeetCode #78): https://leetcode.com/problems/subsets/description/

[54] N-Queens (LeetCode #51): https://leetcode.com/problems/n-queens/description/

[55] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/PermutationsBacktracking.java

[56] Climbing Stairs (LeetCode #70): https://leetcode.com/problems/climbing-stairs/description/

[57] House Robber (LeetCode #198): https://leetcode.com/problems/house-robber/description/

[58] Coin Change (LeetCode #322): https://leetcode.com/problems/coin-change/description/

[59] Longest Common Subsequence (LCS) (LeetCode #1143): https://leetcode.com/problems/longest-common-subsequence/description/

[60] Longest Increasing Subsequence (LIS) (LeetCode #322): https://leetcode.com/problems/longest-increasing-subsequence/description/

[61] Partition Equal Subset Sum (LeetCode #416): https://leetcode.com/problems/partition-equal-subset-sum/description/

[62] ссылка: https://github.com/madela-team/Java/blob/master/leet-code-algorithms/src/main/java/org/madela/FibonacciDP.java

[63] ссылка: https://github.com/madela-team/Java/tree/master/leet-code-algorithms/src

[64] Источник: https://habr.com/ru/articles/964104/?utm_source=habrahabr&utm_medium=rss&utm_campaign=964104