#medium
Задача: 105. Construct Binary Tree from Preorder and Inorder Traversal
Даны два массива целых чисел: preorder и inorder, где preorder — это результат преордер обхода бинарного дерева, а inorder — результат инордер обхода того же дерева. Постройте и верните бинарное дерево.
Пример:
👨💻 Алгоритм:
1️⃣ Создайте хеш-таблицу для записи соотношения значений и их индексов в массиве inorder, чтобы можно было быстро найти позицию корня.
2️⃣ Инициализируйте переменную целочисленного типа preorderIndex для отслеживания элемента, который будет использоваться для создания корня. Реализуйте рекурсивную функцию arrayToTree, которая принимает диапазон массива inorder и возвращает построенное бинарное дерево:
Если диапазон пуст, возвращается null;
Инициализируйте корень элементом preorder[preorderIndex], затем увеличьте preorderIndex;
Рекурсивно используйте левую и правую части массива inorder для построения левого и правого поддеревьев.
3️⃣ Просто вызовите функцию рекурсии с полным диапазоном массива inorder.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 105. Construct Binary Tree from Preorder and Inorder Traversal
Даны два массива целых чисел: preorder и inorder, где preorder — это результат преордер обхода бинарного дерева, а inorder — результат инордер обхода того же дерева. Постройте и верните бинарное дерево.
Пример:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
Если диапазон пуст, возвращается null;
Инициализируйте корень элементом preorder[preorderIndex], затем увеличьте preorderIndex;
Рекурсивно используйте левую и правую части массива inorder для построения левого и правого поддеревьев.
func buildTree(preorder []int, inorder []int) *TreeNode {
preorderIndex := 0
inorderIndexMap := make(map[int]int)
for i := 0; i < len(inorder); i++ {
inorderIndexMap[inorder[i]] = i
}
var arrayToTree func(left int, right int) *TreeNode
arrayToTree = func(left int, right int) *TreeNode {
if left > right {
return nil
}
rootValue := preorder[preorderIndex]
preorderIndex++
root := &TreeNode{Val: rootValue}
root.Left = arrayToTree(left, inorderIndexMap[rootValue]-1)
root.Right = arrayToTree(inorderIndexMap[rootValue]+1, right)
return root
}
return arrayToTree(0, len(preorder)-1)
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#medium
Задача: 106. Construct Binary Tree from Inorder and Postorder Traversal
Даны два массива целых чисел: inorder и postorder, где inorder — это массив обхода в глубину бинарного дерева слева направо, а postorder — это массив обхода в глубину после обработки всех потомков узла. Постройте и верните соответствующее бинарное дерево.
Пример:
👨💻 Алгоритм:
1️⃣ Создайте хэш-таблицу (hashmap) для хранения соответствия значений и их индексов в массиве обхода inorder.
2️⃣ Определите вспомогательную функцию
3️⃣ Выберите последний элемент в массиве обхода postorder в качестве корня. Значение корня имеет индекс
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 106. Construct Binary Tree from Inorder and Postorder Traversal
Даны два массива целых чисел: inorder и postorder, где inorder — это массив обхода в глубину бинарного дерева слева направо, а postorder — это массив обхода в глубину после обработки всех потомков узла. Постройте и верните соответствующее бинарное дерево.
Пример:
Input: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
Output: [3,9,20,null,null,15,7]
helper, которая принимает границы левой и правой части текущего поддерева в массиве inorder. Эти границы используются для проверки, пусто ли поддерево. Если левая граница больше правой (in_left > in_right), то поддерево пустое и функция возвращает None.index в обходе inorder. Элементы от in_left до index - 1 принадлежат левому поддереву, а элементы от index + 1 до in_right — правому поддереву. Согласно логике обхода postorder, сначала рекурсивно строится правое поддерево helper(index + 1, in_right), а затем левое поддерево helper(in_left, index - 1). Возвращается корень.func buildTree(inorder []int, postorder []int) *TreeNode {
idx_map := map[int]int{}
post_idx := len(postorder) - 1
var helper func(in_left int, in_right int) *TreeNode
helper = func(in_left int, in_right int) *TreeNode {
if in_left > in_right {
return nil
}
root_val := postorder[post_idx]
root := &TreeNode{Val: root_val}
index := idx_map[root_val]
post_idx--
root.Right = helper(index+1, in_right)
root.Left = helper(in_left, index-1)
return root
}
for i := 0; i < len(inorder); i++ {
idx_map[inorder[i]] = i
}
return helper(0, len(inorder)-1)
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔1
#medium
Задача: 107. Binary Tree Level Order Traversal II
Дан корень бинарного дерева. Верните обход значений узлов снизу вверх по уровням (то есть слева направо, уровень за уровнем, от листа к корню).
Пример:
👨💻 Алгоритм:
1️⃣ Инициализируйте список вывода levels. Длина этого списка определяет, какой уровень в данный момент обновляется. Вам следует сравнить этот уровень len(levels) с уровнем узла level, чтобы убедиться, что вы добавляете узел на правильный уровень. Если вы все еще находитесь на предыдущем уровне, добавьте новый уровень, вставив новый список в levels.
2️⃣ Добавьте значение узла в последний уровень в levels.
3️⃣ Рекурсивно обработайте дочерние узлы, если они не равны None, используя функцию helper(node.left / node.right, level + 1).
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 107. Binary Tree Level Order Traversal II
Дан корень бинарного дерева. Верните обход значений узлов снизу вверх по уровням (то есть слева направо, уровень за уровнем, от листа к корню).
Пример:
Input: root = [3,9,20,null,null,15,7]
Output: [[15,7],[9,20],[3]]
func levelOrderBottom(root *TreeNode) [][]int {
levels := make([][]int, 0)
var helper func(node *TreeNode, level int)
helper = func(node *TreeNode, level int) {
if node == nil {
return
}
if len(levels) == level {
levels = append(levels, []int{})
}
levels[level] = append(levels[level], node.Val)
if node.Left != nil {
helper(node.Left, level+1)
}
if node.Right != nil {
helper(node.Right, level+1)
}
}
helper(root, 0)
for i := 0; i < len(levels)/2; i++ {
levels[i], levels[len(levels)-1-i] = levels[len(levels)-1-i], levels[i]
}
return levels
}Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Идущий к IT
10$ за техническое собеседование на английском языке:
1. Отправьте запись технического собеседования на английском языке файлом на этот аккаунт
2. Добавьте ссылку на вакансию или пришлите название компании и должность
3. Напишите номер кошелка USDT (Tether) на который отправить 10$
🛡 Важно:
– Запись будет использована только для сбора данных о вопросах
– Вы останетесь анонимны
– Запись нигде не будет опубликована
🤝 Условия:
– Внятный звук, различимая речь
– Допустимые профессии:
• Любые программисты
• DevOps
• Тестировщики
• Дата сайнтисты
• Бизнес/Системные аналитики
• Прожекты/Продукты
• UX/UI и продукт дизайнеры
1. Отправьте запись технического собеседования на английском языке файлом на этот аккаунт
2. Добавьте ссылку на вакансию или пришлите название компании и должность
3. Напишите номер кошелка USDT (Tether) на который отправить 10$
– Запись будет использована только для сбора данных о вопросах
– Вы останетесь анонимны
– Запись нигде не будет опубликована
– Внятный звук, различимая речь
– Допустимые профессии:
• Любые программисты
• DevOps
• Тестировщики
• Дата сайнтисты
• Бизнес/Системные аналитики
• Прожекты/Продукты
• UX/UI и продукт дизайнеры
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1
#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), начиная с корня дерева.
func sortedArrayToBST(nums []int) *TreeNode {
return helper(nums, 0, len(nums)-1)
}
func helper(nums []int, left int, right int) *TreeNode {
if left > right {
return nil
}
p := (left + right) / 2
root := &TreeNode{Val: nums[p]}
root.Left = helper(nums, left, p-1)
root.Right = helper(nums, p+1, right)
return root
}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.
type ListNode struct {
Val int
Next *ListNode
}
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func sortedListToBST(head *ListNode) *TreeNode {
if head == nil {
return nil
}
mid := findMiddleElement(head)
node := &TreeNode{
Val: mid.Val,
}
if head == mid {
return node
}
node.Left = sortedListToBST(head)
node.Right = sortedListToBST(mid.Next)
return node
}
func findMiddleElement(head *ListNode) *ListNode {
var prevPtr *ListNode = nil
slowPtr := head
fastPtr := head
for fastPtr != nil && fastPtr.Next != nil {
prevPtr = slowPtr
slowPtr = slowPtr.Next
fastPtr = fastPtr.Next.Next
}
if prevPtr != nil {
prevPtr.Next = nil
}
return slowPtr
}Please open Telegram to view this post
VIEW IN TELEGRAM
😁1🤔1
#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).
func height(root *TreeNode) int {
if root == nil {
return -1
}
return 1 + max(height(root.Left), height(root.Right))
}
func isBalanced(root *TreeNode) bool {
if root == nil {
return true
}
return abs(height(root.Left)-height(root.Right)) < 2 &&
isBalanced(root.Left) &&
isBalanced(root.Right)
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func abs(a int) int {
if a < 0 {
return -a
}
return a
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔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.
func minDepth(root *TreeNode) int {
var dfs func(root *TreeNode) int
dfs = func(root *TreeNode) int {
if root == nil {
return 0
}
if root.Left == nil {
return 1 + dfs(root.Right)
} else if root.Right == nil {
return 1 + dfs(root.Left)
}
leftDepth := dfs(root.Left)
rightDepth := dfs(root.Right)
if leftDepth < rightDepth {
return 1 + leftDepth
} else {
return 1 + rightDepth
}
}
return dfs(root)
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🤔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.
func hasPathSum(root *TreeNode, sum int) bool {
if root == nil {
return false
}
nodeStack := []*TreeNode{}
sumStack := []int{}
nodeStack = append(nodeStack, root)
sumStack = append(sumStack, sum-root.Val)
for len(nodeStack) > 0 {
lastIdx := len(nodeStack) - 1
currentNode := nodeStack[lastIdx]
nodeStack = nodeStack[:lastIdx]
currSum := sumStack[lastIdx]
sumStack = sumStack[:lastIdx]
if currentNode.Left == nil && currentNode.Right == nil && currSum == 0 {
return true
}
if currentNode.Right != nil {
nodeStack = append(nodeStack, currentNode.Right)
sumStack = append(sumStack, currSum-currentNode.Right.Val)
}
if currentNode.Left != nil {
nodeStack = append(nodeStack, currentNode.Left)
sumStack = append(sumStack, currSum-currentNode.Left.Val)
}
}
return false
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#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
func pathSum(root *TreeNode, sum int) [][]int {
pathsList := [][]int{}
pathNodes := []int{}
var recurseTree func(*TreeNode, int, []int, [][]int) [][]int
recurseTree = func(node *TreeNode, remainingSum int, pathNodes []int, pathsList [][]int) [][]int {
if node == nil {
return pathsList
}
pathNodes = append(pathNodes, node.Val)
if remainingSum == node.Val && node.Left == nil && node.Right == nil {
tmp := make([]int, len(pathNodes))
copy(tmp, pathNodes)
pathsList = append(pathsList, tmp)
} else {
pathsList = recurseTree(node.Left, remainingSum-node.Val, pathNodes, pathsList)
pathsList = recurseTree(node.Right, remainingSum-node.Val, pathNodes, pathsList)
}
pathNodes = pathNodes[:len(pathNodes)-1]
return pathsList
}
return recurseTree(root, sum, pathNodes, pathsList)
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#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
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func flattenTree(node *TreeNode) *TreeNode {
if node == nil {
return nil
}
if node.Left == nil && node.Right == nil {
return node
}
leftTail := flattenTree(node.Left)
rightTail := flattenTree(node.Right)
if leftTail != nil {
leftTail.Right = node.Right
node.Right = node.Left
node.Left = nil
}
if rightTail != nil {
return rightTail
} else {
return leftTail
}
}
func flatten(root *TreeNode) {
flattenTree(root)
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔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
func numDistinct(s string, t string) int {
memo := make(map[string]int)
var helper func(i int, j int) int
helper = func(i int, j int) int {
if i == len(s) || j == len(t) || len(s)-i < len(t)-j {
if j == len(t) {
return 1
}
return 0
}
key := strconv.Itoa(i) + "," + strconv.Itoa(j)
if val, ok := memo[key]; ok {
return val
}
ans := helper(i+1, j)
if s[i] == t[j] {
ans += helper(i+1, j+1)
}
memo[key] = ans
return ans
}
return helper(0, 0)
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔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.
Мы начинаем с добавления корня дерева в очередь. Поскольку на уровне 0 есть только один узел, нам не нужно устанавливать какие-либо соединения, и мы можем перейти к циклу while.
func connect(root *Node) *Node {
if root == nil {
return root
}
Q := []*Node{root}
for len(Q) > 0 {
size := len(Q)
for i := 0; i < size; i++ {
node := Q[0]
Q = Q[1:]
if i < size-1 {
node.Next = Q[0]
}
if node.Left != nil {
Q = append(Q, node.Left)
}
if node.Right != nil {
Q = append(Q, node.Right)
}
}
}
return root
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔2👍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.
func connect(root *Node) *Node {
if root == nil {
return root
}
Q := list.New()
Q.PushBack(root)
for Q.Len() > 0 {
size := Q.Len()
for i := 0; i < size; i++ {
front := Q.Front()
node := front.Value.(*Node)
Q.Remove(front)
if i < size-1 {
node.Next = Q.Front().Value.(*Node)
}
if node.Left != nil {
Q.PushBack(node.Left)
}
if node.Right != nil {
Q.PushBack(node.Right)
}
}
}
return root
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#easy
Задача: 118. Pascal's Triangle
Дано целое число numRows. Верните первые numRows строк треугольника Паскаля.
В треугольнике Паскаля каждое число является суммой двух чисел, непосредственно расположенных над ним, как показано:
Пример:
👨💻 Алгоритм:
Хотя алгоритм очень простой, итерационный подход к построению треугольника Паскаля можно классифицировать как динамическое программирование, поскольку мы строим каждую строку, опираясь на предыдущую.
Сначала мы создаём общий список треугольника, который будет хранить каждую строку как подсписок. Затем мы проверяем особый случай, когда число строк равно 0, так как в противном случае мы бы вернули [1]. Поскольку numRows всегда больше 0, мы можем инициализировать треугольник первой строкой [1] и продолжить заполнять строки следующим образом:
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 118. Pascal's Triangle
Дано целое число numRows. Верните первые numRows строк треугольника Паскаля.
В треугольнике Паскаля каждое число является суммой двух чисел, непосредственно расположенных над ним, как показано:
Пример:
Input: numRows = 5
Output: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
Хотя алгоритм очень простой, итерационный подход к построению треугольника Паскаля можно классифицировать как динамическое программирование, поскольку мы строим каждую строку, опираясь на предыдущую.
Сначала мы создаём общий список треугольника, который будет хранить каждую строку как подсписок. Затем мы проверяем особый случай, когда число строк равно 0, так как в противном случае мы бы вернули [1]. Поскольку numRows всегда больше 0, мы можем инициализировать треугольник первой строкой [1] и продолжить заполнять строки следующим образом:
func generate(numRows int) [][]int {
triangle := make([][]int, numRows)
triangle[0] = append(triangle[0], 1)
for rowNum := 1; rowNum < numRows; rowNum++ {
row := make([]int, 0)
prevRow := triangle[rowNum-1]
row = append(row, 1)
for j := 1; j < rowNum; j++ {
row = append(row, prevRow[j-1]+prevRow[j])
}
row = append(row, 1)
triangle[rowNum] = row
}
return triangle
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#easy
Задача: 119. Pascal's Triangle ll
Для данного целого числа rowIndex верните rowIndex-ю строку (с индексацией с нуля) треугольника Паскаля.
Пример:
👨💻 Алгоритм:
1️⃣ Давайте представим, что у нас есть функция getNum(rowIndex, colIndex), которая дает нам число с индексом colIndex в строке с индексом rowIndex. Мы могли бы просто построить k-ю строку, повторно вызывая getNum(...) для столбцов от 0 до k.
2️⃣ Мы можем сформулировать нашу интуицию в следующую рекурсию:
getNum(rowIndex, colIndex) = getNum(rowIndex - 1, colIndex - 1) + getNum(rowIndex - 1, colIndex)
3️⃣ Рекурсия заканчивается в некоторых известных базовых случаях:
Первая строка - это просто одинокая 1, то есть getNum(0, ...) = 1
Первое и последнее число каждой строки равно 1, то есть getNum(k, 0) = getNum(k, k) = 1
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 119. Pascal's Triangle ll
Для данного целого числа rowIndex верните rowIndex-ю строку (с индексацией с нуля) треугольника Паскаля.
Пример:
Input: rowIndex = 3
Output: [1,3,3,1]
getNum(rowIndex, colIndex) = getNum(rowIndex - 1, colIndex - 1) + getNum(rowIndex - 1, colIndex)
Первая строка - это просто одинокая 1, то есть getNum(0, ...) = 1
Первое и последнее число каждой строки равно 1, то есть getNum(k, 0) = getNum(k, k) = 1
func getNum(row int, col int) int {
if row == 0 || col == 0 || row == col {
return 1
}
return getNum(row-1, col-1) + getNum(row-1, col)
}
func getRow(rowIndex int) []int {
ans := make([]int, rowIndex+1)
for i := 0; i <= rowIndex; i++ {
ans[i] = getNum(rowIndex, i)
}
return ans
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#medium
Задача: 120. Triangle
Дан массив в виде треугольника, верните минимальную сумму пути сверху вниз.
На каждом шаге вы можете перейти к соседнему числу в строке ниже. Более формально, если вы находитесь на индексе i в текущей строке, вы можете перейти либо к индексу i, либо к индексу i + 1 в следующей строке.
Пример:
👨💻 Алгоритм:
1️⃣ Простейший способ реализации этого заключается в перезаписи входных данных, то есть в использовании алгоритма "на месте". В подходе 2 мы рассмотрим, как модифицировать алгоритм так, чтобы он не перезаписывал входные данные, но при этом не требовал более чем O(n) дополнительного пространства.
2️⃣ Когда этот алгоритм завершит свою работу, каждая ячейка (строка, столбец) входного треугольника будет перезаписана минимальной суммой пути от (0, 0) (вершины треугольника) до (строка, столбец) включительно.
Совет на собеседовании: Алгоритмы "на месте" перезаписывают входные данные для экономии места, но иногда это может вызвать проблемы. Вот несколько ситуаций, когда алгоритм "на месте" может быть неуместен.
3️⃣ 1. Алгоритму необходимо работать в многопоточной среде, без эксклюзивного доступа к массиву. Другим потокам может потребоваться читать массив тоже, и они могут не ожидать, что он будет изменен.
2. Даже если поток один, или у алгоритма есть эксклюзивный доступ к массиву во время выполнения, массив может потребоваться повторно использовать позже или другим потоком после освобождения блокировки.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 120. Triangle
Дан массив в виде треугольника, верните минимальную сумму пути сверху вниз.
На каждом шаге вы можете перейти к соседнему числу в строке ниже. Более формально, если вы находитесь на индексе i в текущей строке, вы можете перейти либо к индексу i, либо к индексу i + 1 в следующей строке.
Пример:
Input: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
Output: 11
Explanation: The triangle looks like:
2
3 4
6 5 7
4 1 8 3
The minimum path sum from top to bottom is 2 + 3 + 5 + 1 = 11 (underlined above).
Совет на собеседовании: Алгоритмы "на месте" перезаписывают входные данные для экономии места, но иногда это может вызвать проблемы. Вот несколько ситуаций, когда алгоритм "на месте" может быть неуместен.
2. Даже если поток один, или у алгоритма есть эксклюзивный доступ к массиву во время выполнения, массив может потребоваться повторно использовать позже или другим потоком после освобождения блокировки.
func minimumTotal(triangle [][]int) int {
for row := 1; row < len(triangle); row++ {
for col := 0; col <= row; col++ {
smallestAbove := math.MaxInt32
if col > 0 {
smallestAbove = triangle[row-1][col-1]
}
if col < row {
smallestAbove = min(smallestAbove, triangle[row-1][col])
}
triangle[row][col] += smallestAbove
}
}
return minSlice(triangle[len(triangle)-1])
}
func minSlice(slice []int) int {
minVal := slice[0]
for _, val := range slice {
minVal = min(minVal, val)
}
return minVal
}
func min(a int, b int) int {
if a < b {
return a
}
return b
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#easy
Задача: 121. Best Time to Buy and Sell Stock
Вам дан массив цен, где prices[i] является ценой данной акции в i-й день.
Ваша задача — максимизировать вашу прибыль, выбрав один день для покупки акции и другой день в будущем для ее продажи.
Верните максимальную прибыль, которую вы можете получить от этой операции. Если прибыль получить невозможно, верните 0.
Пример:
👨💻 Алгоритм:
Ключевые моменты на графике — это пики и впадины. Нам нужно найти наибольшую цену после каждой впадины, разница между которыми может быть максимальной прибылью. Мы можем поддерживать две переменные — minprice и maxprofit, соответствующие наименьшей впадине и максимальной прибыли (максимальной разнице между ценой продажи и minprice), полученной на данный момент соответственно.
😎 Решение:
🔥 ТОП ВОПРОСОВ С СОБЕСОВ
🔒 База собесов | 🔒 База тестовых
Задача: 121. Best Time to Buy and Sell Stock
Вам дан массив цен, где prices[i] является ценой данной акции в i-й день.
Ваша задача — максимизировать вашу прибыль, выбрав один день для покупки акции и другой день в будущем для ее продажи.
Верните максимальную прибыль, которую вы можете получить от этой операции. Если прибыль получить невозможно, верните 0.
Пример:
Input: prices = [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell.
Ключевые моменты на графике — это пики и впадины. Нам нужно найти наибольшую цену после каждой впадины, разница между которыми может быть максимальной прибылью. Мы можем поддерживать две переменные — minprice и maxprofit, соответствующие наименьшей впадине и максимальной прибыли (максимальной разнице между ценой продажи и minprice), полученной на данный момент соответственно.
func maxProfit(prices []int) int {
minprice := int(^uint(0) >> 1)
maxprofit := 0
for i := 0; i < len(prices); i++ {
if prices[i] < minprice {
minprice = prices[i]
} else if prices[i]-minprice > maxprofit {
maxprofit = prices[i] - minprice
}
}
return maxprofit
}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.
func maxProfit(prices []int) int {
i := 0
valley := prices[0]
peak := prices[0]
maxprofit := 0
for i < len(prices)-1 {
for i < len(prices)-1 && prices[i] >= prices[i+1] {
i++
}
valley = prices[i]
for i < len(prices)-1 && prices[i] <= prices[i+1] {
i++
}
peak = prices[i]
maxprofit += peak - valley
}
return maxprofit
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#hard
Задача: 124. Binary Tree Maximum Path Sum
Вам дан массив цен, где prices[i] — это цена данной акции в i-й день.
Найдите максимальную прибыль, которую вы можете получить. Вы можете совершить не более двух транзакций.
Обратите внимание: вы не можете участвовать в нескольких транзакциях одновременно (то есть, вы должны продать акцию, прежде чем снова её купить).
Пример:
👨💻 Алгоритм:
1️⃣ Наивная реализация этой идеи заключалась бы в разделении последовательностей на две части и последующем перечислении каждой из подпоследовательностей, хотя это определенно не самое оптимизированное решение.
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
одна из общих техник, которую мы можем применить для оптимизации временной сложности, называется динамическим программированием (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]
func max(x int, y int) int {
if x > y {
return x
}
return y
}
func min(x int, y int) int {
if x > y {
return y
}
return x
}
func maxProfit(prices []int) int {
length := len(prices)
if length <= 1 {
return 0
}
leftMin := prices[0]
rightMax := prices[length-1]
leftProfits := make([]int, length)
rightProfits := make([]int, length+1)
for l := 1; l < length; l++ {
leftProfits[l] = max(leftProfits[l-1], prices[l]-leftMin)
leftMin = min(leftMin, prices[l])
r := length - 1 - l
rightProfits[r] = max(rightProfits[r+1], rightMax-prices[r])
rightMax = max(rightMax, prices[r])
}
maxProfit := 0
for i := 0; i < length; i++ {
maxProfit = max(maxProfit, leftProfits[i]+rightProfits[i+1])
}
return maxProfit
}Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#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. Продолжайте перемещение внутрь, пока указатели не встретятся в середине.
func isPalindrome(s string) bool {
i := 0
j := len(s) - 1
for i < j {
for i < j && !isalnum(s[i]) {
i++
}
for i < j && !isalnum(s[j]) {
j--
}
if tolower(s[i]) != tolower(s[j]) {
return false
}
i++
j--
}
return true
}
func isalnum(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') ||
('A' <= c && c <= 'Z')
}
func tolower(c byte) byte {
if 'A' <= c && c <= 'Z' {
return c - 'A' + 'a'
}
return c
}Please open Telegram to view this post
VIEW IN TELEGRAM
😁3🤔1