#easy
Задача: 108. Convert Sorted Array to Binary Search Tree
Дан массив целых чисел nums, элементы которого отсортированы в порядке возрастания. Преобразуйте его в сбалансированное по высоте двоичное дерево поиска.
Пример:
👨💻 Алгоритм:
1️⃣ Инициализация функции помощника: Реализуйте функцию помощника helper(left, right), которая строит двоичное дерево поиска (BST) из элементов массива nums между индексами left и right.
Если left > right, это означает, что элементов для построения поддерева нет, возвращаем None.
2️⃣ Выбор корня и разделение массива:
Выберите элемент в середине для корня: p = (left + right) // 2.
Инициализируйте корень: root = TreeNode(nums[p]).
3️⃣ Рекурсивное построение поддеревьев:
Рекурсивно стройте левое поддерево: root.left = helper(left, p - 1).
Рекурсивно стройте правое поддерево: root.right = helper(p + 1, right).
В качестве результата возвращайте helper(0, len(nums) - 1), начиная с корня дерева.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 108. Convert Sorted Array to Binary Search Tree
Дан массив целых чисел nums, элементы которого отсортированы в порядке возрастания. Преобразуйте его в сбалансированное по высоте двоичное дерево поиска.
Пример:
Input: nums = [-10,-3,0,5,9]
Output: [0,-3,9,-10,null,5]
Explanation: [0,-10,5,null,-3,null,9] is also accepted:
Если left > right, это означает, что элементов для построения поддерева нет, возвращаем None.
Выберите элемент в середине для корня: p = (left + right) // 2.
Инициализируйте корень: root = TreeNode(nums[p]).
Рекурсивно стройте левое поддерево: root.left = helper(left, p - 1).
Рекурсивно стройте правое поддерево: root.right = helper(p + 1, right).
В качестве результата возвращайте helper(0, len(nums) - 1), начиная с корня дерева.
class Solution:
def sortedArrayToBST(self, nums):
def helper(left, right):
if left > right:
return None
p = (left + right) // 2
root = TreeNode(nums[p])
root.left = helper(left, p - 1)
root.right = helper(p + 1, right)
return root
return helper(0, len(nums) - 1)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
#medium
Задача: 109. Convert Sorted List to Binary Search Tree
Дана голова односвязного списка, элементы которого отсортированы в порядке возрастания. Преобразуйте его в сбалансированное по высоте бинарное дерево поиска.
Пример:
👨💻 Алгоритм:
1️⃣ Поскольку нам дан односвязный список, а не массив, у нас нет прямого доступа к элементам списка по индексам. Нам нужно определить средний элемент односвязного списка. Мы можем использовать подход с двумя указателями для нахождения среднего элемента списка. В основном, у нас есть два указателя: slow_ptr и fast_ptr. slow_ptr перемещается на один узел за раз, тогда как fast_ptr перемещается на два узла за раз. К тому времени, как fast_ptr достигнет конца списка, slow_ptr окажется в середине списка. Для списка с четным количеством элементов любой из двух средних элементов может стать корнем BST.
2️⃣ Как только мы находим средний элемент списка, мы отсоединяем часть списка слева от среднего элемента. Мы делаем это, сохраняя prev_ptr, который указывает на узел перед slow_ptr, т.е. prev_ptr.next = slow_ptr. Для отсоединения левой части мы просто делаем prev_ptr.next = None.
3️⃣ Для создания сбалансированного по высоте BST нам нужно передать только голову связанного списка в функцию, которая преобразует его в BST. Таким образом, мы рекурсивно работаем с левой половиной списка, передавая оригинальную голову списка, и с правой половиной, передавая slow_ptr.next в качестве головы.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 109. Convert Sorted List to Binary Search Tree
Дана голова односвязного списка, элементы которого отсортированы в порядке возрастания. Преобразуйте его в сбалансированное по высоте бинарное дерево поиска.
Пример:
Input: head = [-10,-3,0,5,9]
Output: [0,-3,9,-10,null,5]
Explanation: One possible answer is [0,-3,9,-10,null,5], which represents the shown height balanced BST.
class Solution:
def findMiddle(self, head: ListNode) -> ListNode:
prevPtr = None
slowPtr = head
fastPtr = head
while fastPtr and fastPtr.next:
prevPtr = slowPtr
slowPtr = slowPtr.next
fastPtr = fastPtr.next.next
if prevPtr:
prevPtr.next = None
return slowPtr
def sortedListToBST(self, head: ListNode) -> TreeNode:
if not head:
return None
mid = self.findMiddle(head)
node = TreeNode(mid.val)
if head == mid:
return node
node.left = self.sortedListToBST(head)
node.right = self.sortedListToBST(mid.next)
return node
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👍2
#easy
Задача: 110. Balanced Binary Tree
Дано бинарное дерево, определите, является ли оно
сбалансированным по высоте.
Пример:
👨💻 Алгоритм:
1️⃣ Сначала мы определяем функцию height, которая для любого узла p в дереве T возвращает:
-1, если p является пустым поддеревом, т.е. null;
1 + max(height(p.left), height(p.right)) в противном случае.
2️⃣ Теперь, когда у нас есть метод для определения высоты дерева, остается только сравнить высоты детей каждого узла. Дерево T с корнем r является сбалансированным тогда и только тогда, когда высоты его двух детей отличаются не более чем на 1 и поддеревья каждого ребенка также сбалансированы.
3️⃣ Следовательно, мы можем сравнить высоты двух дочерних поддеревьев, а затем рекурсивно проверить каждое из них:
Если root == NULL, возвращаем true.
Если abs(height(root.left) - height(root.right)) > 1, возвращаем false.
В противном случае возвращаем isBalanced(root.left) && isBalanced(root.right).
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 110. Balanced Binary Tree
Дано бинарное дерево, определите, является ли оно
сбалансированным по высоте.
Пример:
Input: root = [3,9,20,null,null,15,7]
Output: true
-1, если p является пустым поддеревом, т.е. null;
1 + max(height(p.left), height(p.right)) в противном случае.
Если root == NULL, возвращаем true.
Если abs(height(root.left) - height(root.right)) > 1, возвращаем false.
В противном случае возвращаем isBalanced(root.left) && isBalanced(root.right).
class Solution:
def height(self, root: TreeNode) -> int:
if not root:
return -1
return 1 + max(self.height(root.left), self.height(root.right))
def isBalanced(self, root: TreeNode) -> bool:
if not root:
return True
return (
abs(self.height(root.left) - self.height(root.right)) < 2
and self.isBalanced(root.left)
and self.isBalanced(root.right)
)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
#easy
Задача: 111. Minimum Depth of Binary Tree
Дано бинарное дерево, найдите его минимальную глубину.
Минимальная глубина - это количество узлов вдоль самого короткого пути от корневого узла до ближайшего листового узла.
Пример:
👨💻 Алгоритм:
1️⃣ Мы будем использовать метод обхода в глубину (dfs) с корнем в качестве аргумента.
Базовое условие рекурсии: это для узла NULL, в этом случае мы должны вернуть 0.
2️⃣ Если левый ребенок корня является NULL: тогда мы должны вернуть 1 + минимальную глубину для правого ребенка корневого узла, что равно 1 + dfs(root.right).
3️⃣ Если правый ребенок корня является NULL: тогда мы должны вернуть 1 + минимальную глубину для левого ребенка корневого узла, что равно 1 + dfs(root.left). Если оба ребенка не являются NULL, тогда вернуть 1 + min(dfs(root.left), dfs(root.right)).
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 111. Minimum Depth of Binary Tree
Дано бинарное дерево, найдите его минимальную глубину.
Минимальная глубина - это количество узлов вдоль самого короткого пути от корневого узла до ближайшего листового узла.
Пример:
Input: root = [3,9,20,null,null,15,7]
Output: 2
Базовое условие рекурсии: это для узла NULL, в этом случае мы должны вернуть 0.
class Solution:
def minDepth(self, root):
def dfs(root):
if root is None:
return 0
if root.left is None:
return 1 + dfs(root.right)
elif root.right is None:
return 1 + dfs(root.left)
return 1 + min(dfs(root.left), dfs(root.right))
return dfs(root)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4👀1
#easy
Задача: 112. Path Sum
Дан корень бинарного дерева и целое число targetSum. Верните true, если в дереве существует путь от корня до листа, такой, что сумма всех значений вдоль пути равна targetSum.
Лист — это узел без детей.
Пример:
👨💻 Алгоритм:
1️⃣ Инициализация стека: Начать с помещения в стек корневого узла и соответствующей оставшейся суммы, равной sum - root.val.
2️⃣ Обработка узлов: Извлечь текущий узел из стека и вернуть True, если оставшаяся сумма равна 0 и узел является листом.
3️⃣ Добавление дочерних узлов в стек: Если оставшаяся сумма не равна нулю или узел не является листом, добавить в стек дочерние узлы с соответствующими оставшимися суммами.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 112. Path Sum
Дан корень бинарного дерева и целое число targetSum. Верните true, если в дереве существует путь от корня до листа, такой, что сумма всех значений вдоль пути равна targetSum.
Лист — это узел без детей.
Пример:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
Output: true
Explanation: The root-to-leaf path with the target sum is shown.
class Solution:
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if not root:
return False
de = [
(root, sum - root.val),
]
while de:
node, curr_sum = de.pop()
if not node.left and not node.right and curr_sum == 0:
return True
if node.right:
de.append((node.right, curr_sum - node.right.val))
if node.left:
de.append((node.left, curr_sum - node.left.val))
return False
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
#Medium
Задача: 113. Path Sum II
Дан корень бинарного дерева и целое число targetSum. Верните все пути от корня до листа, где сумма значений узлов в пути равна targetSum. Каждый путь должен быть возвращён как список значений узлов, а не ссылок на узлы.
Путь от корня до листа — это путь, начинающийся от корня и заканчивающийся на любом листовом узле. Лист — это узел без детей.
Пример:
👨💻 Алгоритм:
1️⃣ Определение функции recurseTree: Функция принимает текущий узел (node), оставшуюся сумму (remainingSum), которая необходима для продолжения поиска вниз по дереву, и список узлов (pathNodes), который содержит все узлы, встреченные до текущего момента на данной ветке.
2️⃣ Проверка условий: На каждом шаге проверяется, равна ли оставшаяся сумма значению текущего узла. Если это так и текущий узел является листом, текущий путь (pathNodes) добавляется в итоговый список путей, который должен быть возвращен.
3️⃣ Обработка всех ветвей: Учитывая, что значения узлов могут быть отрицательными, необходимо исследовать все ветви дерева до самых листьев, независимо от текущей суммы по пути.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 113. Path Sum II
Дан корень бинарного дерева и целое число targetSum. Верните все пути от корня до листа, где сумма значений узлов в пути равна targetSum. Каждый путь должен быть возвращён как список значений узлов, а не ссылок на узлы.
Путь от корня до листа — это путь, начинающийся от корня и заканчивающийся на любом листовом узле. Лист — это узел без детей.
Пример:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
Output: [[5,4,11,2],[5,8,4,5]]
Explanation: There are two paths whose sum equals targetSum:
5 + 4 + 11 + 2 = 22
5 + 8 + 4 + 5 = 22
class TreeNode:
def __init__(self, x: int) -> None:
self.val = x
self.left = None
self.right = None
class Solution:
def recurseTree(
self,
node: TreeNode,
remainingSum: int,
pathNodes: List[int],
pathsList: List[List[int]],
) -> None:
if not node:
return
pathNodes.append(node.val)
if remainingSum == node.val and not node.left and not node.right:
pathsList.append(list(pathNodes))
else:
self.recurseTree(
node.left, remainingSum - node.val, pathNodes, pathsList
)
self.recurseTree(
node.right, remainingSum - node.val, pathNodes, pathsList
)
pathNodes.pop()
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
pathsList = []
self.recurseTree(root, sum, [], pathsList)
return pathsList
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2
#Medium
Задача: 114. Flatten Binary Tree to Linked List
"Связный список" должен использовать тот же класс TreeNode, где указатель на правого ребенка указывает на следующий узел в списке, а указатель на левого ребенка всегда равен null.
"Связный список" должен быть в том же порядке, что и обход бинарного дерева в прямом порядке.
Пример:
👨💻 Алгоритм:
1️⃣ Плоское преобразование дерева: Рекурсивно преобразуем левое и правое поддеревья заданного узла, сохраняя соответствующие конечные узлы в leftTail и rightTail.
2️⃣ Установка соединений: Если у текущего узла есть левый ребенок, выполняем следующие соединения:
leftTail.right = node.right
node.right = node.left
node.left = None
3️⃣ Возврат конечного узла: Возвращаем rightTail, если у узла есть правый ребенок, иначе возвращаем leftTail.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 114. Flatten Binary Tree to Linked List
"Связный список" должен использовать тот же класс TreeNode, где указатель на правого ребенка указывает на следующий узел в списке, а указатель на левого ребенка всегда равен null.
"Связный список" должен быть в том же порядке, что и обход бинарного дерева в прямом порядке.
Пример:
Input: root = [1,2,5,3,4,null,6]
Output: [1,null,2,null,3,null,4,null,5,null,6]
leftTail.right = node.right
node.right = node.left
node.left = None
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def flattenTree(self, node: TreeNode) -> TreeNode:
if not node:
return None
if not node.left and not node.right:
return node
leftTail = self.flattenTree(node.left)
rightTail = self.flattenTree(node.right)
if leftTail:
leftTail.right = node.right
node.right = node.left
node.left = None
return rightTail if rightTail else leftTail
def flatten(self, root: TreeNode) -> None:
self.flattenTree(root)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1😁1
#hard
Задача: 115. Distinct Subsequences
"Даны две строки s и t. Верните количество различных подпоследовательностей строки s, которые равны строке t.
Тестовые примеры сгенерированы таким образом, что ответ укладывается в 32-битное целое число со знаком."
Пример:
👨💻 Алгоритм:
1️⃣ Определите функцию с названием recurse, которая принимает два целочисленных значения i и j. Первое значение представляет текущий обрабатываемый символ в строке S, а второе - текущий символ в строке T. Инициализируйте словарь под названием memo, который будет кэшировать результаты различных вызовов рекурсии.**
2️⃣ Проверьте базовый случай. Если одна из строк закончилась, возвращается 0 или 1 в зависимости от того, удалось ли обработать всю строку T или нет. Есть ещё один базовый случай, который следует рассмотреть. Если оставшаяся длина строки S меньше, чем у строки T, то совпадение невозможно. Если это обнаруживается, то рекурсия также обрезается и возвращается 0.**
3️⃣ Затем проверяем, существует ли текущая пара индексов в нашем словаре. Если да, то просто возвращаем сохранённое/кэшированное значение. Если нет, то продолжаем обычную обработку. Сравниваем символы s[i] и t[j]. Сохраняем результат вызова recurse(i + 1, j) в переменную. Как упоминалось ранее, результат этой рекурсии необходим, независимо от совпадения символов. Если символы совпадают, добавляем к переменной результат вызова recurse(i + 1, j + 1). Наконец, сохраняем значение этой переменной в словаре с ключом (i, j) и возвращаем это значение в качестве ответа.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 115. Distinct Subsequences
"Даны две строки s и t. Верните количество различных подпоследовательностей строки s, которые равны строке t.
Тестовые примеры сгенерированы таким образом, что ответ укладывается в 32-битное целое число со знаком."
Пример:
Input: s = "rabbbit", t = "rabbit"
Output: 3
Explanation:
As shown below, there are 3 ways you can generate "rabbit" from s.
rabbbit
rabbbit
rabbbit
class Solution:
def numDistinct(self, s: str, t: str) -> int:
memo = {}
def uniqueSubsequences(i: int, j: int) -> int:
M, N = len(s), len(t)
if i == M or j == N or M - i < N - j:
return int(j == len(t))
if (i, j) in memo:
return memo[i, j]
ans = uniqueSubsequences(i + 1, j)
if s[i] == t[j]:
ans += uniqueSubsequences(i + 1, j + 1)
memo[i, j] = ans
return ans
return uniqueSubsequences(0, 0)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
#medium
Задача: 116. Populating Next Right Pointers in Each Node
Вам дано идеальное бинарное дерево, где все листья находятся на одном уровне, и у каждого родителя есть два детей. Бинарное дерево имеет следующее определение:
Заполните каждый указатель
Изначально все указатели
Пример:
👨💻 Алгоритм:
1️⃣ Инициализируйте очередь Q, которую мы будем использовать во время обхода. Существует несколько способов реализации обхода в ширину, особенно когда речь идет о определении уровня конкретного узла. Можно добавлять в очередь пару (узел, уровень), и каждый раз, когда добавляются дети узла, мы добавляем (node.left, parent_level + 1) и (node.right, parent_level + 1). Этот подход не будет очень эффективен для нашего алгоритма, так как нам нужны все узлы на одном уровне, и для этого потребуется еще одна структура данных.
2️⃣ Более эффективный с точки зрения памяти способ разделения узлов одного уровня заключается в использовании некоторой разграничительной метки между уровнями. Обычно мы вставляем в очередь элемент NULL, который отмечает конец предыдущего уровня и начало следующего. Это отличный подход, но он все равно потребует некоторого количества памяти, пропорционально количеству уровней в дереве.
3️⃣ Подход, который мы будем использовать здесь, будет иметь структуру вложенных циклов, чтобы обойти необходимость указателя NULL. По сути, на каждом шаге мы записываем размер очереди, который всегда соответствует всем узлам на определенном уровне. Как только мы узнаем этот размер, мы обрабатываем только это количество элементов и не более. К моменту, когда мы закончим обработку заданного количества элементов, в очереди будут все узлы следующего уровня. Вот псевдокод для этого:
Мы начинаем с добавления корня дерева в очередь. Поскольку на уровне 0 есть только один узел, нам не нужно устанавливать какие-либо соединения, и мы можем перейти к циклу while.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 116. Populating Next Right Pointers in Each Node
Вам дано идеальное бинарное дерево, где все листья находятся на одном уровне, и у каждого родителя есть два детей. Бинарное дерево имеет следующее определение:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}Заполните каждый указатель
next так, чтобы он указывал на следующий правый узел. Если следующего правого узла нет, указатель next должен быть установлен в NULL.Изначально все указатели
next установлены в NULL.Пример:
Input: root = [1,2,3,4,5,6,7]
Output: [1,#,2,3,#,4,5,6,7,#]
Explanation: Given the above perfect binary tree (Figure A), your function should populate each next pointer to point to its next right node, just like in Figure B. The serialized output is in level order as connected by the next pointers, with '#' signifying the end of each level.
while (!Q.empty())
{
size = Q.size()
for i in range 0..size
{
node = Q.pop()
Q.push(node.left)
Q.push(node.right)
}
}
Мы начинаем с добавления корня дерева в очередь. Поскольку на уровне 0 есть только один узел, нам не нужно устанавливать какие-либо соединения, и мы можем перейти к циклу while.
import collections
class Solution:
def connect(self, root: "Node") -> "Node":
if not root:
return root
Q = collections.deque([root])
while Q:
size = len(Q)
for i in range(size):
node = Q.popleft()
if i < size - 1:
node.next = Q[0]
if node.left:
Q.append(node.left)
if node.right:
Q.append(node.right)
return root
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
#medium
Задача: 117. Populating Next Right Pointers in Each Node II
Вам дано бинарное дерево:
Заполните каждый указатель
Изначально все указатели
Пример:
👨💻 Алгоритм:
1️⃣ Инициализируйте очередь Q, которая будет использоваться во время обхода. Существует несколько способов реализации обхода в ширину, особенно когда дело доходит до определения уровня конкретного узла. Можно добавить в очередь пару (узел, уровень), и каждый раз, когда мы добавляем детей узла, мы добавляем (node.left, parent_level+1) и (node.right, parent_level+1). Этот подход может оказаться неэффективным для нашего алгоритма, так как нам нужны все узлы на одном уровне, и потребуется дополнительная структура данных.
2️⃣ Более эффективный с точки зрения памяти способ разделения узлов одного уровня - использовать разграничение между уровнями. Обычно мы вставляем в очередь элемент NULL, который обозначает конец предыдущего уровня и начало следующего. Это отличный подход, но он все равно потребует некоторого объема памяти, пропорционального количеству уровней в дереве.
3️⃣ Подход, который мы будем использовать, предполагает структуру вложенных циклов, чтобы обойти необходимость NULL указателя. По сути, на каждом шаге мы фиксируем размер очереди, который всегда соответствует всем узлам на определенном уровне. Как только мы узнаем этот размер, мы обрабатываем только это количество элементов и больше ничего. К тому времени, как мы закончим обработку заданного количества элементов, в очереди окажутся все узлы следующего уровня. Вот псевдокод для этого:
Мы начинаем с добавления корня дерева в очередь. Так как на уровне 0 есть только один узел, нам не нужно устанавливать какие-либо соединения, и мы можем перейти к циклу while.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 117. Populating Next Right Pointers in Each Node II
Вам дано бинарное дерево:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}Заполните каждый указатель
next, чтобы он указывал на следующий правый узел. Если следующего правого узла нет, указатель next должен быть установлен в NULL.Изначально все указатели
next установлены в NULL.Пример:
Input: root = [1,2,3,4,5,null,7]
Output: [1,#,2,3,#,4,5,7,#]
Explanation: Given the above binary tree (Figure A), your function should populate each next pointer to point to its next right node, just like in Figure B. The serialized output is in level order as connected by the next pointers, with '#' signifying the end of each level.
while (!Q.empty())
{
size = Q.size()
for i in range 0..size
{
node = Q.pop()
Q.push(node.left)
Q.push(node.right)
}
}
Мы начинаем с добавления корня дерева в очередь. Так как на уровне 0 есть только один узел, нам не нужно устанавливать какие-либо соединения, и мы можем перейти к циклу while.
class Solution:
def connect(self, root: Optional["Node"]) -> Optional["Node"]:
if not root:
return root
Q = collections.deque([root])
while Q:
size = len(Q)
for i in range(size):
node = Q.popleft()
if i < size - 1:
node.next = Q[0]
if node.left:
Q.append(node.left)
if node.right:
Q.append(node.right)
return root
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
#medium
Задача: 122. Best Time to Buy and Sell Stock II
Вам дан массив целых чисел prices, где prices[i] — это цена данной акции в i-й день.
Каждый день вы можете решить купить и/или продать акцию. Вы можете держать в руках только одну акцию за раз. Однако вы можете купить её и сразу же продать в тот же день.
Найдите и верните максимальную прибыль, которую вы можете получить.
Пример:
👨💻 Алгоритм:
1️⃣ Предположим, что дан массив:
\[ 7, 1, 5, 3, 6, 4 \]
Если мы изобразим числа данного массива на графике, мы получим: *приложенное изображение*
2️⃣ Анализируя график, мы замечаем, что точки интереса — это последовательные впадины и пики.
Математически говоря:
Общая прибыль = ∑ (высота(пик i) - высота(впадина i))
3️⃣ Ключевой момент заключается в том, что мы должны учитывать каждый пик, следующий сразу за впадиной, чтобы максимизировать прибыль. Если мы пропустим один из пиков (пытаясь получить больше прибыли), мы потеряем прибыль по одной из сделок, что приведет к общей меньшей прибыли.
Например, в вышеуказанном случае, если мы пропустим пик i и впадину j, пытаясь получить больше прибыли, рассматривая точки с большей разницей в высотах, чистая прибыль всегда будет меньше, чем та, которая была бы получена при их учете, поскольку C всегда будет меньше, чем A+B.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 122. Best Time to Buy and Sell Stock II
Вам дан массив целых чисел prices, где prices[i] — это цена данной акции в i-й день.
Каждый день вы можете решить купить и/или продать акцию. Вы можете держать в руках только одну акцию за раз. Однако вы можете купить её и сразу же продать в тот же день.
Найдите и верните максимальную прибыль, которую вы можете получить.
Пример:
Input: prices = [7,1,5,3,6,4]
Output: 7
Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4.
Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.
Total profit is 4 + 3 = 7.
\[ 7, 1, 5, 3, 6, 4 \]
Если мы изобразим числа данного массива на графике, мы получим: *приложенное изображение*
Математически говоря:
Общая прибыль = ∑ (высота(пик i) - высота(впадина i))
Например, в вышеуказанном случае, если мы пропустим пик i и впадину j, пытаясь получить больше прибыли, рассматривая точки с большей разницей в высотах, чистая прибыль всегда будет меньше, чем та, которая была бы получена при их учете, поскольку C всегда будет меньше, чем A+B.
class Solution:
def maxProfit(self, prices: List[int]) -> int:
i = 0
valley = prices[0]
peak = prices[0]
maxprofit = 0
while i < len(prices) - 1:
while i < len(prices) - 1 and prices[i] >= prices[i + 1]:
i += 1
valley = prices[i]
while i < len(prices) - 1 and prices[i] <= prices[i + 1]:
i += 1
peak = prices[i]
maxprofit += peak - valley
return maxprofit
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
#hard
Задача: 124. Binary Tree Maximum Path Sum
Вам дан массив цен, где prices[i] — это цена данной акции в i-й день.
Найдите максимальную прибыль, которую вы можете получить. Вы можете совершить не более двух транзакций.
Пример:
👨💻 Алгоритм:
1️⃣ Наивная реализация этой идеи заключалась бы в разделении последовательностей на две части и последующем перечислении каждой из подпоследовательностей, хотя это определенно не самое оптимизированное решение.
Для последовательности длиной N у нас было бы N возможных разделений (включая отсутствие разделения), каждый элемент был бы посещен один раз в каждом разделении. В результате общая временная сложность этой наивной реализации составила бы O(N²).
2️⃣ Мы могли бы сделать лучше, чем наивная реализация O(N²). Что касается алгоритмов разделяй и властвуй,
одна из общих техник, которую мы можем применить для оптимизации временной сложности, называется динамическим программированием (DP), где мы меняем менее повторяющиеся вычисления на некоторое дополнительное пространство.
В алгоритмах динамического программирования обычно мы создаем массив одного или двух измерений для сохранения промежуточных оптимальных результатов. В данной задаче мы бы использовали два массива, один массив сохранял бы результаты последовательности слева направо, а другой массив сохранял бы результаты последовательности справа налево. Для удобства мы могли бы назвать это двунаправленным динамическим программированием.
3️⃣ Сначала мы обозначаем последовательность цен как Prices[i], с индексом начиная от 0 до N-1. Затем мы определяем два массива, а именно left_profits[i] и right_profits[i].
Как следует из названия, каждый элемент в массиве left_profits[i] будет содержать максимальную прибыль, которую можно получить от выполнения одной транзакции в левой подпоследовательности цен от индекса ноль до i, (т.е. Prices[0], Prices[1], ... Prices[i]).
Например, для подпоследовательности [7, 1, 5] соответствующий left_profits[2] будет равен 4, что означает покупку по цене 1 и продажу по цене 5.
И каждый элемент в массиве right_profits[i] будет содержать максимальную прибыль, которую можно получить от выполнения одной транзакции в правой подпоследовательности цен от индекса i до N-1, (т.е. Prices[i], Prices[i+1], ... Prices[N-1]).
Например, для правой подпоследовательности [3, 6, 4] соответствующий right_profits[3] будет равен 3, что означает покупку по цене 3 и продажу по цене 6.
Теперь, если мы разделим последовательность цен вокруг элемента с индексом i на две подпоследовательности, с левыми подпоследовательностями как Prices[0], Prices[1], ... Prices[i] и правой подпоследовательностью как Prices[i+1], ... Prices[N-1],
то общая максимальная прибыль, которую мы получим от этого разделения (обозначенная как max_profits[i]), может быть выражена следующим образом:
max_profits[i] = left_profits[i] + right_profits[i+1]
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 124. Binary Tree Maximum Path Sum
Вам дан массив цен, где prices[i] — это цена данной акции в i-й день.
Найдите максимальную прибыль, которую вы можете получить. Вы можете совершить не более двух транзакций.
Пример:
Input: prices = [3,3,5,0,0,3,1,4]
Output: 6
Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1
Для последовательности длиной N у нас было бы N возможных разделений (включая отсутствие разделения), каждый элемент был бы посещен один раз в каждом разделении. В результате общая временная сложность этой наивной реализации составила бы O(N²).
одна из общих техник, которую мы можем применить для оптимизации временной сложности, называется динамическим программированием (DP), где мы меняем менее повторяющиеся вычисления на некоторое дополнительное пространство.
В алгоритмах динамического программирования обычно мы создаем массив одного или двух измерений для сохранения промежуточных оптимальных результатов. В данной задаче мы бы использовали два массива, один массив сохранял бы результаты последовательности слева направо, а другой массив сохранял бы результаты последовательности справа налево. Для удобства мы могли бы назвать это двунаправленным динамическим программированием.
Как следует из названия, каждый элемент в массиве left_profits[i] будет содержать максимальную прибыль, которую можно получить от выполнения одной транзакции в левой подпоследовательности цен от индекса ноль до i, (т.е. Prices[0], Prices[1], ... Prices[i]).
Например, для подпоследовательности [7, 1, 5] соответствующий left_profits[2] будет равен 4, что означает покупку по цене 1 и продажу по цене 5.
И каждый элемент в массиве right_profits[i] будет содержать максимальную прибыль, которую можно получить от выполнения одной транзакции в правой подпоследовательности цен от индекса i до N-1, (т.е. Prices[i], Prices[i+1], ... Prices[N-1]).
Например, для правой подпоследовательности [3, 6, 4] соответствующий right_profits[3] будет равен 3, что означает покупку по цене 3 и продажу по цене 6.
Теперь, если мы разделим последовательность цен вокруг элемента с индексом i на две подпоследовательности, с левыми подпоследовательностями как Prices[0], Prices[1], ... Prices[i] и правой подпоследовательностью как Prices[i+1], ... Prices[N-1],
то общая максимальная прибыль, которую мы получим от этого разделения (обозначенная как max_profits[i]), может быть выражена следующим образом:
max_profits[i] = left_profits[i] + right_profits[i+1]
class Solution(object):
def maxProfit(self, prices: List[int]) -> int:
if len(prices) <= 1:
return 0
left_min = prices[0]
right_max = prices[-1]
length = len(prices)
left_profits = [0] * length
# pad the right DP array with an additional zero for convenience.
right_profits = [0] * (length + 1)
# construct the bidirectional DP array
for l in range(1, length):
left_profits[l] = max(left_profits[l - 1], prices[l] - left_min)
left_min = min(left_min, prices[l])
r = length - 1 - l
right_profits[r] = max(right_profits[r + 1], right_max - prices[r])
right_max = max(right_max, prices[r])
max_profit = 0
for i in range(0, length):
max_profit = max(max_profit, left_profits[i] + right_profits[i + 1])
return max_profit
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
#easy
Задача: 125. Valid Palindrome
Фраза является палиндромом, если после преобразования всех заглавных букв в строчные и удаления всех неалфавитно-цифровых символов она читается одинаково как слева направо, так и справа налево. Алфавитно-цифровые символы включают в себя буквы и цифры.
Для данной строки s верните true, если она является палиндромом, и false в противном случае.
Пример:
👨💻 Алгоритм:
1️⃣ Поскольку входная строка содержит символы, которые нам нужно игнорировать при проверке на палиндром, становится трудно определить реальную середину нашего палиндромического ввода.
2️⃣ Вместо того, чтобы двигаться от середины наружу, мы можем двигаться внутрь к середине! Если начать перемещаться внутрь, с обоих концов входной строки, мы можем ожидать увидеть те же символы в том же порядке.
3️⃣ Результирующий алгоритм прост:
1. Установите два указателя, один на каждом конце входной строки.
2. Если ввод является палиндромом, оба указателя должны указывать на эквивалентные символы в любой момент времени.
3. Если это условие не выполняется в какой-либо момент времени, мы прерываемся и возвращаемся раньше.
4. Мы можем просто игнорировать неалфавитно-цифровые символы, продолжая двигаться дальше.
5. Продолжайте перемещение внутрь, пока указатели не встретятся в середине.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 125. Valid Palindrome
Фраза является палиндромом, если после преобразования всех заглавных букв в строчные и удаления всех неалфавитно-цифровых символов она читается одинаково как слева направо, так и справа налево. Алфавитно-цифровые символы включают в себя буквы и цифры.
Для данной строки s верните true, если она является палиндромом, и false в противном случае.
Пример:
Input: s = "A man, a plan, a canal: Panama"
Output: true
Explanation: "amanaplanacanalpanama" is a palindrome.
1. Установите два указателя, один на каждом конце входной строки.
2. Если ввод является палиндромом, оба указателя должны указывать на эквивалентные символы в любой момент времени.
3. Если это условие не выполняется в какой-либо момент времени, мы прерываемся и возвращаемся раньше.
4. Мы можем просто игнорировать неалфавитно-цифровые символы, продолжая двигаться дальше.
5. Продолжайте перемещение внутрь, пока указатели не встретятся в середине.
class Solution:
def isPalindrome(self, s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
while i < j and not s[i].isalnum():
i += 1
while i < j and not s[j].isalnum():
j -= 1
if s[i].lower() != s[j].lower():
return False
i += 1
j -= 1
return True
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
#Hard
Задача: 126.Word Ladder II
Последовательность преобразований от слова beginWord до слова endWord с использованием словаря wordList — это последовательность слов beginWord -> s1 -> s2 -> ... -> sk, для которой выполняются следующие условия:
Каждая пара соседних слов отличается ровно одной буквой.
Каждое si для 1 <= i <= k находится в wordList. Отметим, что beginWord не обязательно должно быть в wordList.
sk == endWord.
Для двух слов, beginWord и endWord, и словаря wordList, вернуть все самые короткие последовательности преобразований от beginWord до endWord или пустой список, если такая последовательность не существует. Каждая последовательность должна возвращаться в виде списка слов [beginWord, s1, s2, ..., sk].
Пример:
👨💻 Алгоритм:
1️⃣ Сохранение слов из списка слов (wordList) в хэш-таблицу (unordered set) для эффективного удаления слов в процессе поиска в ширину (BFS).
2️⃣ Выполнение BFS, добавление связей в список смежности (adjList). После завершения уровня удалять посещенные слова из wordList.
3️⃣ Начать с beginWord и отслеживать текущий путь как currPath, просматривать все возможные пути, и когда путь ведет к endWord, сохранять путь в shortestPaths.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 126.Word Ladder II
Последовательность преобразований от слова beginWord до слова endWord с использованием словаря wordList — это последовательность слов beginWord -> s1 -> s2 -> ... -> sk, для которой выполняются следующие условия:
Каждая пара соседних слов отличается ровно одной буквой.
Каждое si для 1 <= i <= k находится в wordList. Отметим, что beginWord не обязательно должно быть в wordList.
sk == endWord.
Для двух слов, beginWord и endWord, и словаря wordList, вернуть все самые короткие последовательности преобразований от beginWord до endWord или пустой список, если такая последовательность не существует. Каждая последовательность должна возвращаться в виде списка слов [beginWord, s1, s2, ..., sk].
Пример:
Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
Output: [["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
Explanation: There are 2 shortest transformation sequences:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"
class Solution:
def __init__(self):
self.adjList = {}
self.currPath = []
self.shortestPaths = []
def findNeighbors(self, word, wordSet):
neighbors = []
charList = list(word)
for i in range(len(charList)):
oldChar = charList[i]
for c in "abcdefghijklmnopqrstuvwxyz":
if c != oldChar:
charList[i] = c
newWord = "".join(charList)
if newWord in wordSet:
neighbors.append(newWord)
charList[i] = oldChar
return neighbors
def backtrack(self, source, destination):
if source == destination:
self.shortestPaths.append(self.currPath[::-1])
for neighbor in self.adjList.get(source, []):
self.currPath.append(neighbor)
self.backtrack(neighbor, destination)
self.currPath.pop()
def bfs(self, beginWord, endWord, wordSet):
q = deque([beginWord])
wordSet.discard(beginWord)
isEnqueued = {beginWord: True}
while q:
visited = []
for _ in range(len(q)):
currWord = q.popleft()
neighbors = self.findNeighbors(currWord, wordSet)
for neighbor in neighbors:
if neighbor not in isEnqueued:
q.append(neighbor)
isEnqueued[neighbor] = True
self.adjList[neighbor] = []
self.adjList[neighbor].append(currWord)
visited.append(neighbor)
for word in visited:
wordSet.discard(word)
def findLadders(self, beginWord, endWord, wordList):
wordSet = set(wordList)
self.bfs(beginWord, endWord, wordSet)
self.currPath = [endWord]
self.backtrack(endWord, beginWord)
return self.shortestPaths
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
#Hard
Задача: 127. Word Ladder
Секвенция трансформации от слова beginWord к слову endWord с использованием словаря wordList представляет собой последовательность слов beginWord -> s1 -> s2 -> ... -> sk, при которой:
Каждая пара соседних слов отличается ровно одной буквой.
Каждый элемент si для 1 <= i <= k присутствует в wordList. Отметим, что beginWord не обязан быть в wordList.
sk равно endWord.
Для двух слов, beginWord и endWord, и словаря wordList, верните количество слов в кратчайшей секвенции трансформации от beginWord к endWord, или 0, если такая секвенция не существует.
Пример:
👨💻 Алгоритм:
1️⃣ Препроцессинг списка слов: Осуществите препроцессинг заданного списка слов (wordList), чтобы найти все возможные промежуточные состояния слов. Сохраните эти состояния в словаре, где ключом будет промежуточное слово, а значением — список слов, имеющих то же промежуточное состояние.
2️⃣ Использование очереди для обхода: Поместите в очередь кортеж, содержащий
3️⃣ Поиск кратчайшего пути через BFS (обход в ширину): Пока в очереди есть элементы, получите первый элемент очереди. Для каждого слова определите все промежуточные преобразования и проверьте, не являются ли эти преобразования также преобразованиями других слов из списка. Для каждого найденного слова, которое имеет общее промежуточное состояние с текущим словом, добавьте в очередь пару (слово, уровень + 1), где уровень — это уровень текущего слова. Если вы достигли искомого слова, его уровень покажет длину кратчайшей последовательности преобразования.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 127. Word Ladder
Секвенция трансформации от слова beginWord к слову endWord с использованием словаря wordList представляет собой последовательность слов beginWord -> s1 -> s2 -> ... -> sk, при которой:
Каждая пара соседних слов отличается ровно одной буквой.
Каждый элемент si для 1 <= i <= k присутствует в wordList. Отметим, что beginWord не обязан быть в wordList.
sk равно endWord.
Для двух слов, beginWord и endWord, и словаря wordList, верните количество слов в кратчайшей секвенции трансформации от beginWord к endWord, или 0, если такая секвенция не существует.
Пример:
Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: One shortest transformation sequence is "hit" -> "hot" -> "dot" -> "dog" -> cog", which is 5 words long.
beginWord и число 1, где 1 обозначает уровень узла. Вам нужно вернуть уровень узла endWord, так как он будет представлять длину кратчайшей последовательности преобразования. Используйте словарь посещений, чтобы избежать циклов.from collections import defaultdict, deque
from typing import List
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList or not endWord or not beginWord or not wordList:
return 0
L = len(beginWord)
all_combo_dict = defaultdict(list)
for word in wordList:
for i in range(L):
all_combo_dict[word[:i] + "*" + word[i + 1:]].append(word)
queue = deque([(beginWord, 1)])
visited = {beginWord: True}
while queue:
current_word, level = queue.popleft()
for i in range(L):
intermediate_word = current_word[:i] + "*" + current_word[i + 1:]
for word in all_combo_dict[intermediate_word]:
if word == endWord:
return level + 1
if word not in visited:
visited[word] = True
queue.append((word, level + 1))
all_combo_dict[intermediate_word] = []
return 0
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
#Medium
Задача: 129. Sum Root to Leaf Numbers
Вам дан корень бинарного дерева, содержащего только цифры от 0 до 9.
Каждый путь от корня до листа в дереве представляет собой число.
Например, путь от корня до листа 1 -> 2 -> 3 представляет число 123.
Верните общую сумму всех чисел от корня до листа. Тестовые случаи созданы таким образом, что ответ поместится в 32-битное целое число.
Листовой узел — это узел без детей.
Пример:
👨💻 Алгоритм:
1️⃣ Инициализация:
Создайте переменные для хранения суммы чисел (rootToLeaf) и текущего числа (currNumber), а также стек для обхода дерева, начиная с корневого узла.
2️⃣ Обход дерева:
Используйте стек для глубинного обхода дерева (DFS), обновляя currNumber путём умножения на 10 и добавления значения узла. Для каждого листа добавляйте currNumber к rootToLeaf.
3️⃣ Возвращение результата:
По завершении обхода всех узлов возвращайте rootToLeaf, содержащую сумму всех чисел от корня до листьев дерева.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 129. Sum Root to Leaf Numbers
Вам дан корень бинарного дерева, содержащего только цифры от 0 до 9.
Каждый путь от корня до листа в дереве представляет собой число.
Например, путь от корня до листа 1 -> 2 -> 3 представляет число 123.
Верните общую сумму всех чисел от корня до листа. Тестовые случаи созданы таким образом, что ответ поместится в 32-битное целое число.
Листовой узел — это узел без детей.
Пример:
Input: root = [1,2,3]
Output: 25
Explanation:
The root-to-leaf path 1->2 represents the number 12.
The root-to-leaf path 1->3 represents the number 13.
Therefore, sum = 12 + 13 = 25.
Создайте переменные для хранения суммы чисел (rootToLeaf) и текущего числа (currNumber), а также стек для обхода дерева, начиная с корневого узла.
Используйте стек для глубинного обхода дерева (DFS), обновляя currNumber путём умножения на 10 и добавления значения узла. Для каждого листа добавляйте currNumber к rootToLeaf.
По завершении обхода всех узлов возвращайте rootToLeaf, содержащую сумму всех чисел от корня до листьев дерева.
class Solution:
def sumNumbers(self, root: TreeNode) -> int:
root_to_leaf = curr_number = 0
while root:
if root.left:
predecessor = root.left
steps = 1
while predecessor.right and predecessor.right is not root:
predecessor = predecessor.right
steps += 1
if predecessor.right is None:
curr_number = curr_number * 10 + root.val
predecessor.right = root
root = root.left
else:
if predecessor.left is None:
root_to_leaf += curr_number
for _ in range(steps):
curr_number //= 10
predecessor.right = None
root = root.right
else:
curr_number = curr_number * 10 + root.val
if root.right is None:
root_to_leaf += curr_number
root = root.right
return root_to_leaf
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
#Medium
Задача: 130. Surrounded Regions
Вам дана матрица размером m на n, которая содержит буквы 'X' и 'O'. Захватите регионы, которые окружены:
Соединение: Ячейка соединена с соседними ячейками по горизонтали или вертикали.
Регион: Для формирования региона соедините каждую ячейку 'O'.
Окружение: Регион окружён ячейками 'X', если можно соединить регион с ячейками 'X', и ни одна из ячеек региона не находится на краю доски.
Окруженный регион захватывается путём замены всех 'O' на 'X' в исходной матрице.
Пример:
👨💻 Алгоритм:
1️⃣ Выбор начальных ячеек и инициация DFS:
Начинаем с выбора всех ячеек, расположенных на границах доски.
Затем, начиная с каждой выбранной ячейки на границе, выполняем обход в глубину (DFS).
2️⃣ Логика и выполнение DFS:
Если ячейка на границе оказывается 'O', это означает, что эта ячейка "жива", вместе с другими ячейками 'O', соединёнными с этой граничной ячейкой. Две ячейки считаются соединёнными, если между ними существует путь, состоящий только из букв 'O'.
Цель нашего обхода DFS будет заключаться в том, чтобы отметить все такие связанные ячейки 'O', которые исходят из границы, каким-либо отличительным символом, например, 'E'.
3️⃣ Классификация и финальная обработка ячеек:
После обхода всех граничных ячеек мы получаем три типа ячеек:
Ячейки с буквой 'X': эти ячейки можно считать стеной.
Ячейки с буквой 'O': эти ячейки не затрагиваются в нашем обходе DFS, то есть они не имеют соединения с границей, следовательно, они захвачены. Эти ячейки следует заменить на букву 'X'.
Ячейки с буквой 'E': это ячейки, отмеченные в ходе нашего обхода DFS, то есть ячейки, имеющие хотя бы одно соединение с границами, следовательно, они не захвачены. В результате мы должны вернуть этим ячейкам их исходную букву 'O'.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 130. Surrounded Regions
Вам дана матрица размером m на n, которая содержит буквы 'X' и 'O'. Захватите регионы, которые окружены:
Соединение: Ячейка соединена с соседними ячейками по горизонтали или вертикали.
Регион: Для формирования региона соедините каждую ячейку 'O'.
Окружение: Регион окружён ячейками 'X', если можно соединить регион с ячейками 'X', и ни одна из ячеек региона не находится на краю доски.
Окруженный регион захватывается путём замены всех 'O' на 'X' в исходной матрице.
Пример:
Input: board = [["X"]]
Output: [["X"]]
Начинаем с выбора всех ячеек, расположенных на границах доски.
Затем, начиная с каждой выбранной ячейки на границе, выполняем обход в глубину (DFS).
Если ячейка на границе оказывается 'O', это означает, что эта ячейка "жива", вместе с другими ячейками 'O', соединёнными с этой граничной ячейкой. Две ячейки считаются соединёнными, если между ними существует путь, состоящий только из букв 'O'.
Цель нашего обхода DFS будет заключаться в том, чтобы отметить все такие связанные ячейки 'O', которые исходят из границы, каким-либо отличительным символом, например, 'E'.
После обхода всех граничных ячеек мы получаем три типа ячеек:
Ячейки с буквой 'X': эти ячейки можно считать стеной.
Ячейки с буквой 'O': эти ячейки не затрагиваются в нашем обходе DFS, то есть они не имеют соединения с границей, следовательно, они захвачены. Эти ячейки следует заменить на букву 'X'.
Ячейки с буквой 'E': это ячейки, отмеченные в ходе нашего обхода DFS, то есть ячейки, имеющие хотя бы одно соединение с границами, следовательно, они не захвачены. В результате мы должны вернуть этим ячейкам их исходную букву 'O'.
class Solution(object):
def solve(self, board: List[List[str]]) -> None:
if not board or not board[0]:
return
self.ROWS = len(board)
self.COLS = len(board[0])
from itertools import product
borders = list(product(range(self.ROWS), [0, self.COLS - 1])) + list(
product([0, self.ROWS - 1], range(self.COLS))
)
for row, col in borders:
self.DFS(board, row, col)
for r in range(self.ROWS):
for c in range(self.COLS):
if board[r][c] == "O":
board[r][c] = "X"
elif board[r][c] == "E":
board[r][c] = "O"
def DFS(self, board: List[List[str]], row: int, col: int) -> None:
if board[row][col] != "O":
return
board[row][col] = "E"
if col < self.COLS - 1:
self.DFS(board, row, col + 1)
if row < self.ROWS - 1:
self.DFS(board, row + 1, col)
if col > 0:
self.DFS(board, row, col - 1)
if row > 0:
self.DFS(board, row - 1, col)
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
#Medium
Задача: 131. Palindrome Partitioning
Дана строка s. Разделите строку таким образом, чтобы каждая подстрока разделения была палиндромом. Верните все возможные варианты разделения строки s на палиндромы.
Пример:
👨💻 Алгоритм:
1️⃣ Инициация рекурсивного обхода:
В алгоритме обратного отслеживания (backtracking) мы рекурсивно пробегаем по строке, используя метод поиска в глубину (depth-first search). Для каждого рекурсивного вызова задаётся начальный индекс строки start.
Итеративно генерируем все возможные подстроки, начиная с индекса start. Индекс end увеличивается от start до конца строки.
2️⃣ Проверка на палиндром и продолжение поиска:
Для каждой сгенерированной подстроки проверяем, является ли она палиндромом.
Если подстрока оказывается палиндромом, она становится потенциальным кандидатом. Добавляем подстроку в currentList и выполняем поиск в глубину для оставшейся части строки. Если текущая подстрока заканчивается на индексе end, то end+1 становится начальным индексом для следующего рекурсивного вызова.
3️⃣ Возврат (Backtracking) и сохранение результатов:
Возвращаемся, если начальный индекс start больше или равен длине строки, и добавляем currentList в результат.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 131. Palindrome Partitioning
Дана строка s. Разделите строку таким образом, чтобы каждая подстрока разделения была палиндромом. Верните все возможные варианты разделения строки s на палиндромы.
Пример:
Input: s = "aab"
Output: [["a","a","b"],["aa","b"]]
В алгоритме обратного отслеживания (backtracking) мы рекурсивно пробегаем по строке, используя метод поиска в глубину (depth-first search). Для каждого рекурсивного вызова задаётся начальный индекс строки start.
Итеративно генерируем все возможные подстроки, начиная с индекса start. Индекс end увеличивается от start до конца строки.
Для каждой сгенерированной подстроки проверяем, является ли она палиндромом.
Если подстрока оказывается палиндромом, она становится потенциальным кандидатом. Добавляем подстроку в currentList и выполняем поиск в глубину для оставшейся части строки. Если текущая подстрока заканчивается на индексе end, то end+1 становится начальным индексом для следующего рекурсивного вызова.
Возвращаемся, если начальный индекс start больше или равен длине строки, и добавляем currentList в результат.
class Solution:
def partition(self, s: str) -> List[List[str]]:
result = []
self.dfs(s, [], result)
return result
def isPalindrome(self, s: str) -> bool:
return s == s[::-1]
def dfs(self, s: str, path: List[str], result: List[List[str]]):
if not s:
result.append(path)
return
for i in range(1, len(s) + 1):
if self.isPalindrome(s[:i]):
self.dfs(s[i:], path + [s[:i]], result)
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
#Hard
Задача: 132. Palindrome Partitioning II
Дана строка s. Разделите строку так, чтобы каждая подстрока разделения была палиндромом.
Верните минимальное количество разрезов, необходимых для разделения строки s на палиндромы.
Пример:
👨💻 Алгоритм:
1️⃣ Определение задачи и начальные условия:
Алгоритм обратного отслеживания реализуется путём рекурсивного изучения кандидатов-подстрок. Мы определяем рекурсивный метод findMinimumCut, который находит минимальное количество разрезов для подстроки, начинающейся с индекса start и заканчивающейся на индексе end.
Чтобы найти минимальное количество разрезов, мы также должны знать минимальное количество разрезов, которые были найдены ранее для других разделений на палиндромы. Эта информация отслеживается в переменной minimumCut.
Начальное значение minimumCut будет равно максимально возможному количеству разрезов в строке, что равно длине строки минус один (т.е. разрез между каждым символом).
2️⃣ Генерация подстрок и рекурсивный поиск:
Теперь, когда мы знаем начальные и конечные индексы, мы должны сгенерировать все возможные подстроки, начиная с индекса start. Для этого мы будем держать начальный индекс постоянным. currentEndIndex обозначает конец текущей подстроки.
3️⃣ Условие палиндрома и рекурсивное разделение:
Если текущая подстрока является палиндромом, мы сделаем разрез после currentEndIndex и рекурсивно найдем минимальный разрез для оставшейся строки
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 132. Palindrome Partitioning II
Дана строка s. Разделите строку так, чтобы каждая подстрока разделения была палиндромом.
Верните минимальное количество разрезов, необходимых для разделения строки s на палиндромы.
Пример:
Input: s = "aab"
Output: 1
Explanation: The palindrome partitioning ["aa","b"] could be produced using 1 cut.
Алгоритм обратного отслеживания реализуется путём рекурсивного изучения кандидатов-подстрок. Мы определяем рекурсивный метод findMinimumCut, который находит минимальное количество разрезов для подстроки, начинающейся с индекса start и заканчивающейся на индексе end.
Чтобы найти минимальное количество разрезов, мы также должны знать минимальное количество разрезов, которые были найдены ранее для других разделений на палиндромы. Эта информация отслеживается в переменной minimumCut.
Начальное значение minimumCut будет равно максимально возможному количеству разрезов в строке, что равно длине строки минус один (т.е. разрез между каждым символом).
Теперь, когда мы знаем начальные и конечные индексы, мы должны сгенерировать все возможные подстроки, начиная с индекса start. Для этого мы будем держать начальный индекс постоянным. currentEndIndex обозначает конец текущей подстроки.
Если текущая подстрока является палиндромом, мы сделаем разрез после currentEndIndex и рекурсивно найдем минимальный разрез для оставшейся строки
class Solution:
def minCut(self, s: str) -> int:
return self.findMinimumCut(s, 0, len(s) - 1, len(s) - 1)
def findMinimumCut(self, s: str, start: int, end: int, minimumCut: int) -> int:
if start == end or self.isPalindrome(s, start, end):
return 0
for currentEndIndex in range(start, end + 1):
if self.isPalindrome(s, start, currentEndIndex):
minimumCut = min(
minimumCut,
1 + self.findMinimumCut(s, currentEndIndex + 1, end, minimumCut)
)
return minimumCut
def isPalindrome(self, s: str, start: int, end: int) -> bool:
while start < end:
if s[start] != s[end]:
return False
start += 1
end -= 1
return True
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
#medium
Задача: 133. Clone Graph
Дана ссылка на узел в связанном неориентированном графе.
Верните глубокую копию (клон) графа.
Каждый узел в графе содержит значение (целое число) и список (List[Node]) своих соседей.
Пример:
👨💻 Алгоритм:
1️⃣ Используйте хеш-таблицу для хранения ссылок на копии всех уже посещенных и скопированных узлов. Ключом будет узел оригинального графа, а значением — соответствующий клонированный узел клонированного графа. Хеш-таблица посещенных узлов также используется для предотвращения циклов.
2️⃣ Добавьте первый узел в очередь, клонируйте его и добавьте в хеш-таблицу посещенных.
3️⃣ Выполните обход в ширину (BFS): извлеките узел из начала очереди, посетите всех соседей этого узла. Если какой-либо сосед уже был посещен, получите его клон из хеш-таблицы посещенных; если нет, создайте клон и добавьте его в хеш-таблицу. Добавьте клоны соседей в список соседей клонированного узла.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 133. Clone Graph
Дана ссылка на узел в связанном неориентированном графе.
Верните глубокую копию (клон) графа.
Каждый узел в графе содержит значение (целое число) и список (List[Node]) своих соседей.
Пример:
Input: adjList = [[2,4],[1,3],[2,4],[1,3]]
Output: [[2,4],[1,3],[2,4],[1,3]]
Explanation: There are 4 nodes in the graph.
1st node (val = 1)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
2nd node (val = 2)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
3rd node (val = 3)'s neighbors are 2nd node (val = 2) and 4th node (val = 4).
4th node (val = 4)'s neighbors are 1st node (val = 1) and 3rd node (val = 3).
from collections import deque
class Node:
def __init__(self, val=0, neighbors=None):
self.val = val
self.neighbors = neighbors if neighbors is not None else []
class Solution:
def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
if not node:
return node
visited = {}
queue = deque([node])
visited[node] = Node(node.val, [])
while queue:
n = queue.popleft()
for neighbor in n.neighbors:
if neighbor not in visited:
visited[neighbor] = Node(neighbor.val, [])
queue.append(neighbor)
visited[n].neighbors.append(visited[neighbor])
return visited[node]
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2❤1