C# | LeetCode
3.48K subscribers
161 photos
1 file
1.06K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.iss.one/+nebTPWgpeGs1OWFi
Вопросы собесов t.iss.one/+sjKGQXl79ytkYzIy
Вакансии t.iss.one/+BQFHXZQ0zrViNGIy
Download Telegram
Задача: 213. House Robber II
Сложность: medium

Вы профессиональный грабитель, планирующий ограбить дома вдоль улицы. В каждом доме спрятано определенное количество денег. Все дома в этом месте расположены по кругу, что означает, что первый дом является соседом последнего. Между тем, в соседних домах установлена охранная система, которая автоматически свяжется с полицией, если два соседних дома будут взломаны в одну ночь.

Дан массив целых чисел nums, представляющий количество денег в каждом доме, верните максимальную сумму денег, которую вы можете ограбить этой ночью, не вызвав полицию.

Пример:
Input: nums = [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.


👨‍💻 Алгоритм:

1⃣Обработка базовых случаев:
Если массив nums пуст, возвращаем 0.
Если в массиве nums только один дом, возвращаем значение этого дома.

2⃣Разделение задачи на две подзадачи:
Находим максимальную сумму для подмассива домов от первого до предпоследнего, вызывая функцию RobSimple с параметрами 0 и nums.Length - 2.
Находим максимальную сумму для подмассива домов от второго до последнего, вызывая функцию RobSimple с параметрами 1 и nums.Length - 1.

3⃣Сравнение результатов и возврат максимального значения:
Вернуть максимальное значение из двух полученных результатов.

😎 Решение:
public class Solution {
public int Rob(int[] nums) {
if (nums.Length == 0) return 0;
if (nums.Length == 1) return nums[0];

int max1 = RobSimple(nums, 0, nums.Length - 2);
int max2 = RobSimple(nums, 1, nums.Length - 1);

return Math.Max(max1, max2);
}

private int RobSimple(int[] nums, int start, int end) {
int t1 = 0;
int t2 = 0;

for (int i = start; i <= end; i++) {
int temp = t1;
int current = nums[i];
t1 = Math.Max(current + t2, t1);
t2 = temp;
}

return t1;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 63. Unique Paths II
Сложность: medium

Вам дана матрица размером m на n, содержащая целые числа. Робот находится в начальный момент в верхнем левом углу (то есть в ячейке grid[0][0]). Робот пытается добраться до нижнего правого угла (то есть в ячейку grid[m - 1][n - 1]). Робот может двигаться только вниз или вправо в любой момент времени.

Препятствия и свободные пространства отмечены в матрице как 1 и 0 соответственно. Путь, который проходит робот, не может включать клетки, которые являются препятствиями.

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

Тестовые примеры сгенерированы таким образом, что ответ будет не более 2 * 10^9.

Пример:
Input: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
Output: 2
Explanation: There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -> Right -> Down -> Down
2. Down -> Down -> Right -> Right


👨‍💻 Алгоритм:

1⃣Если первая ячейка, то есть obstacleGrid[0,0], содержит 1, это означает, что в первой ячейке есть препятствие. Следовательно, робот не сможет сделать ни одного хода, и мы должны вернуть количество возможных путей как 0. Если же obstacleGrid[0,0] изначально равно 0, мы устанавливаем его равным 1 и продолжаем.

2⃣Итерация по первой строке. Если ячейка изначально содержит 1, это означает, что текущая ячейка имеет препятствие и не должна учитываться в каком-либо пути. Следовательно, значение этой ячейки устанавливается равным 0. В противном случае, устанавливаем его равным значению предыдущей ячейки, то есть obstacleGrid[i,j] = obstacleGrid[i,j-1]. Повторяем аналогичные действия для первого столбца.

3⃣Далее, итерация по массиву начиная с ячейки obstacleGrid[1,1]. Если ячейка изначально не содержит препятствий, то количество способов добраться до этой ячейки будет равно сумме количества способов добраться до ячейки над ней и количества способов добраться до ячейки слева от неё, то есть obstacleGrid[i,j] = obstacleGrid[i-1,j] + obstacleGrid[i,j-1]. Если в ячейке есть препятствие, устанавливаем её значение равным 0 и продолжаем. Это делается для того, чтобы она не учитывалась в других путях.

😎 Решение:
public class Solution {
public int UniquePathsWithObstacles(int[][] obstacleGrid) {
int R = obstacleGrid.Length;
int C = obstacleGrid[0].Length;

if (obstacleGrid[0][0] == 1) {
return 0;
}

obstacleGrid[0][0] = 1;

for (int i = 1; i < R; i++) {
obstacleGrid[i][0] = (obstacleGrid[i][0] == 0 && obstacleGrid[i - 1][0] == 1) ? 1 : 0;
}

for (int i = 1; i < C; i++) {
obstacleGrid[0][i] = (obstacleGrid[0][i] == 0 && obstacleGrid[0][i - 1] == 1) ? 1 : 0;
}

for (int i = 1; i < R; i++) {
for (int j = 1; j < C; j++) {
if (obstacleGrid[i][j] == 0) {
obstacleGrid[i][j] = obstacleGrid[i - 1][j] + obstacleGrid[i][j - 1];
} else {
obstacleGrid[i][j] = 0;
}
}
}

return obstacleGrid[R - 1][C - 1];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1
Задача: 1192. Critical Connections in a Network
Сложение: hard

Существует n серверов, пронумерованных от 0 до n - 1, соединенных неориентированными соединениями "сервер-сервер", образуя сеть, где connections[i] = [ai, bi] представляет собой соединение между серверами ai и bi. Любой сервер может достичь других серверов напрямую или косвенно через сеть.

Критическое соединение — это соединение, удаление которого сделает невозможным достижение некоторыми серверами других серверов.
Верните все критические соединения в сети в любом порядке.

Пример:
Input: n = 4, connections = [[0,1],[1,2],[2,0],[1,3]]
Output: [[1,3]]
Explanation: [[3,1]] is also accepted.


👨‍💻 Алгоритм:

1⃣Построение графа и инициализация:
Постройте граф в виде списка смежности и создайте словарь для хранения соединений.
Инициализируйте ранги для узлов.

2⃣Поиск в глубину (DFS):
Реализуйте функцию dfs для обхода графа.
Обновите ранги узлов и определите минимальный ранг для текущего узла.
Проверьте, можно ли удалить текущее соединение, и обновите минимальный ранг.

3⃣Поиск критических соединений:
После завершения обхода DFS преобразуйте оставшиеся соединения в список и верните его.

😎 Решение:
public class Solution {
private Dictionary<int, List<int>> graph;
private Dictionary<int, int?> rank;
private Dictionary<(int, int), bool> connDict;

public IList<IList<int>> CriticalConnections(int n, IList<IList<int>> connections) {
formGraph(n, connections);
dfs(0, 0);

var result = new List<IList<int>>();
foreach (var conn in connDict.Keys) {
result.Add(new List<int> { conn.Item1, conn.Item2 });
}
return result;
}

private int dfs(int node, int discoveryRank) {
if (rank[node] != null) {
return rank[node].Value;
}

rank[node] = discoveryRank;
int minRank = discoveryRank + 1;

foreach (var neighbor in graph[node]) {
if (rank[neighbor] != null && rank[neighbor] == discoveryRank - 1) {
continue;
}

int recursiveRank = dfs(neighbor, discoveryRank + 1);

if (recursiveRank <= discoveryRank) {
connDict.Remove((Math.Min(node, neighbor), Math.Max(node, neighbor)));
}

minRank = Math.Min(minRank, recursiveRank);
}

return minRank;
}

private void formGraph(int n, IList<IList<int>> connections) {
graph = new Dictionary<int, List<int>>();
rank = new Dictionary<int, int?>();
connDict = new Dictionary<(int, int), bool>();

for (int i = 0; i < n; i++) {
graph[i] = new List<int>();
rank[i] = null;
}

foreach (var edge in connections) {
int u = edge[0], v = edge[1];
graph[u].Add(v);
graph[v].Add(u);
connDict[(Math.Min(u, v), Math.Max(u, v))] = true;
}
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1110. Delete Nodes And Return Forest
Сложность: medium

Дан корень бинарного дерева, каждый узел в дереве имеет уникальное значение.
После удаления всех узлов со значением из to_delete, остаётся лес (несвязное объединение деревьев).

Верните корни деревьев в оставшемся лесу. Вы можете вернуть результат в любом порядке.

Пример:
Input: root = [1,2,3,4,5,6,7], to_delete = [3,5]
Output: [[1,2,null,4],[6],[7]]


👨‍💻 Алгоритм:

1⃣Инициализация:
Преобразуйте массив to_delete в множество toDeleteSet для эффективного поиска.
Создайте пустой список forest для хранения корней деревьев в результирующем лесу.

2⃣Рекурсивный обход:
Выполните обход дерева в порядке пост-ордера, чтобы сначала обработать все дочерние узлы перед текущим узлом (node):
- рекурсивно вызовите processNode для левого и правого дочерних узлов node и обновите левого и правого дочернего узла с возвращаемым значением.

3⃣Оценка узла:
Проверьте, нужно ли удалить текущий узел, проверив, существует ли его значение в toDeleteSet. Если узел нужно удалить:
- если у узла есть левый или правый дочерний узел, добавьте их в forest.
- верните null для его родителя, чтобы эффективно удалить текущий узел, не подключая его обратно к родительскому узлу.
Если узел не нужно удалять, верните сам узел.

😎 Решение:
public class Solution {
public IList<TreeNode> DelNodes(TreeNode root, int[] to_delete) {
var toDeleteSet = new HashSet<int>(to_delete);
var forest = new List<TreeNode>();

root = ProcessNode(root, toDeleteSet, forest);
if (root != null) {
forest.Add(root);
}

return forest;
}

private TreeNode ProcessNode(TreeNode node, HashSet<int> toDeleteSet, List<TreeNode> forest) {
if (node == null) {
return null;
}

node.left = ProcessNode(node.left, toDeleteSet, forest);
node.right = ProcessNode(node.right, toDeleteSet, forest);

if (toDeleteSet.Contains(node.val)) {
if (node.left != null) {
forest.Add(node.left);
}
if (node.right != null) {
forest.Add(node.right);
}
return null;
}

return node;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
Задача: 348. Design Tic-Tac-Toe
Сложность: medium

Предположим, что следующие правила относятся к игре в крестики-нолики на доске размером n x n между двумя игроками:

Ход гарантированно является допустимым и делается на пустом поле.
Как только достигается выигрышное условие, дальнейшие ходы запрещены.
Игрок, который успешно размещает n своих меток в горизонтальном, вертикальном или диагональном ряду, выигрывает игру.
Реализуйте класс TicTacToe:

TicTacToe(int n) Инициализирует объект с размером доски n.
int move(int row, int col, int player) Указывает, что игрок с идентификатором player делает ход в ячейке (row, col) на доске. Ход гарантированно является допустимым, и два игрока ходят по очереди. Возвращает:
0, если после хода победителя нет.
1, если после хода игрок 1 является победителем.
2, если после хода игрок 2 является победителем.

Пример:
Input
["TicTacToe", "move", "move", "move", "move", "move", "move", "move"]
[[3], [0, 0, 1], [0, 2, 2], [2, 2, 1], [1, 1, 2], [2, 0, 1], [1, 0, 2], [2, 1, 1]]
Output
[null, 0, 0, 0, 0, 0, 0, 1]


👨‍💻 Алгоритм:

1⃣Инициализация:
Создайте массивы rows и cols для отслеживания количества маркеров в каждой строке и столбце соответственно.
Создайте переменные diagonal и antiDiagonal для отслеживания количества маркеров на главной и побочной диагоналях соответственно.
Инициализируйте размер доски n.

2⃣Выполнение хода:
Увеличьте счетчики в rows, cols, diagonal и antiDiagonal в зависимости от текущего хода.
Если текущий ход игрока попадает на диагонали, обновите соответствующие переменные diagonal и antiDiagonal.

3⃣Проверка победы:
Проверьте, достиг ли один из счетчиков в rows, cols, diagonal или antiDiagonal значения n (размер доски). Если да, верните идентификатор игрока как победителя.
Если ни одно из условий победы не выполнено, верните 0, что означает отсутствие победителя после текущего хода.

😎 Решение:
public class TicTacToe {
private int[] rows;
private int[] cols;
private int diagonal;
private int antiDiagonal;
private int n;

public TicTacToe(int n) {
this.n = n;
rows = new int[n];
cols = new int[n];
diagonal = 0;
antiDiagonal = 0;
}

public int Move(int row, int col, int player) {
int add = (player == 1) ? 1 : -1;
rows[row] += add;
cols[col] += add;
if (row == col) {
diagonal += add;
}
if (row + col == n - 1) {
antiDiagonal += add;
}
if (Math.Abs(rows[row]) == n || Math.Abs(cols[col]) == n || Math.Abs(diagonal) == n || Math.Abs(antiDiagonal) == n) {
return player;
}
return 0;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1102. Path With Maximum Minimum Value
Сложность: medium

Дана целочисленная матрица grid размером m x n. Верните максимальное значение пути, начинающегося в (0, 0) и заканчивающегося в (m - 1, n - 1), двигаясь в 4 кардинальных направлениях.

Значение пути определяется минимальным числом на этом пути.

Пример:
Input: grid = [[5,4,5],[1,2,6],[7,4,6]]
Output: 4
Explanation: The path with the maximum score is highlighted in yellow.


👨‍💻 Алгоритм:

1⃣Начните с оценки curScore = min(grid[0][0], grid[m-1][n-1]), где m и n - общее количество строк и столбцов входной матрицы.

2⃣Выполните BFS на матрице и проверьте, существует ли путь, где все значения больше или равны curScore:
Используйте очередь (deque) для хранения всех непосещенных ячеек со значением, большим или равным curScore.
Извлекайте ячейку из начала очереди, проверяйте, есть ли у нее непосещенные соседние ячейки, и добавляйте их в конец очереди.
Если успешно достигли правой нижней ячейки, значит путь существует.
Если очередь опустела до достижения правой нижней ячейки, пути не существует.

3⃣Если пути не существует, что означает, что curScore слишком велик, уменьшите его на 1 и повторите шаг 2.
В противном случае, верните curScore как ответ.

😎 Решение:
public class Solution {
private static readonly int[][] dirs = new int[][] {
new int[] {1, 0}, new int[] {0, 1}, new int[] {-1, 0}, new int[] {0, -1}
};

public int MaximumMinimumPath(int[][] grid) {
int R = grid.Length, C = grid[0].Length;
int curScore = Math.Min(grid[0][0], grid[R - 1][C - 1]);

while (curScore >= 0) {
if (PathExists(grid, curScore)) {
return curScore;
} else {
curScore--;
}
}
return -1;
}

private bool PathExists(int[][] grid, int curScore) {
int R = grid.Length, C = grid[0].Length;
bool[][] visited = new bool[R][];
for (int i = 0; i < R; i++) {
visited[i] = new bool[C];
}
Queue<(int, int)> dq = new Queue<(int, int)>();
dq.Enqueue((0, 0));
visited[0][0] = true;

while (dq.Count > 0) {
(int curRow, int curCol) = dq.Dequeue();
if (curRow == R - 1 && curCol == C - 1) {
return true;
}
foreach (var dir in dirs) {
int newRow = curRow + dir[0], newCol = curCol + dir[1];
if (newRow >= 0 && newCol >= 0 && newRow < R && newCol < C && !visited[newRow][newCol] && grid[newRow][newCol] >= curScore) {
dq.Enqueue((newRow, newCol));
visited[newRow][newCol] = true;
}
}
}
return false;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 426. Convert Binary Search Tree to Sorted Doubly Linked List
Сложность: medium

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

Вы можете считать, что указатели "влево" и "вправо" аналогичны указателям на предшественника и последователя в двусвязном списке. Для кольцевого двусвязного списка предшественник первого элемента является последним элементом, а последователь последнего элемента является первым элементом.

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

Пример:
Input: root = [4,2,5,1,3]
Output: [1,2,3,4,5]
Explanation: The figure below shows the transformed BST. The solid line indicates the successor relationship, while the dashed line means the predecessor relationship.


👨‍💻 Алгоритм:

1⃣Инициализируйте первые и последние узлы как null.

2⃣Вызовите стандартную вспомогательную рекурсивную функцию helper(root):
Если узел не равен null:
Вызовите рекурсию для левого поддерева helper(node.left).
Если последний узел не равен null, свяжите последний узел и текущий узел.
Иначе инициализируйте первый узел.
Пометьте текущий узел как последний: last = node.
Вызовите рекурсию для правого поддерева helper(node.right).

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

😎 Решение:
public class Node {
public int val;
public Node left;
public Node right;

public Node() {}

public Node(int _val) {
val = _val;
left = null;
right = null;
}

public Node(int _val, Node _left, Node _right) {
val = _val;
left = _left;
right = _right;
}
}

public class Solution {
private Node first = null;
private Node last = null;

private void Helper(Node node) {
if (node != null) {
Helper(node.left);

if (last != null) {
last.right = node;
node.left = last;
} else {
first = node;
}
last = node;

Helper(node.right);
}
}

public Node TreeToDoublyList(Node root) {
if (root == null) return null;

Helper(root);

last.right = first;
first.left = last;
return first;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 652. Find Duplicate Subtrees
Сложность: medium

Если задан корень бинарного дерева, верните все дублирующие поддеревья. Для каждого вида дублирующих поддеревьев достаточно вернуть корневой узел любого из них. Два дерева являются дублирующими, если они имеют одинаковую структуру с одинаковыми значениями узлов.

Пример:
Input: root = [1,2,3,4,null,2,4,null,null,4]
Output: [[2,4],[4]]


👨‍💻 Алгоритм:

1⃣Выполните обход дерева и используйте сериализацию для представления каждого поддерева.

2⃣Храните все сериализованные представления поддеревьев в хэш-таблице и отслеживайте частоту их появления.

3⃣Найдите поддеревья, которые появляются более одного раза, и верните корневые узлы этих поддеревьев.

😎 Решение:
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}

public class Solution {
public IList<TreeNode> FindDuplicateSubtrees(TreeNode root) {
Dictionary<string, int> count = new Dictionary<string, int>();
List<TreeNode> result = new List<TreeNode>();
Serialize(root, count, result);
return result;
}

private string Serialize(TreeNode node, Dictionary<string, int> count, List<TreeNode> result) {
if (node == null) return "#";
string serial = node.val + "," + Serialize(node.left, count, result) + "," + Serialize(node.right, count, result);
if (count.ContainsKey(serial)) {
count[serial]++;
} else {
count[serial] = 1;
}
if (count[serial] == 2) {
result.Add(node);
}
return serial;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 672. Bulb Switcher II
Сложность: medium

Есть комната с n лампочками, пронумерованными от 1 до n, которые изначально все включены, и четыре кнопки на стене. Каждая из четырех кнопок имеет разную функциональность:

Кнопка 1: Переключает состояние всех лампочек.
Кнопка 2: Переключает состояние всех лампочек с четными номерами (т.е. 2, 4, ...).
Кнопка 3: Переключает состояние всех лампочек с нечетными номерами (т.е. 1, 3, ...).
Кнопка 4: Переключает состояние всех лампочек с номером j = 3k + 1, где k = 0, 1, 2, ... (т.е. 1, 4, 7, 10, ...).
Необходимо сделать ровно presses нажатий кнопок. Для каждого нажатия можно выбрать любую из четырех кнопок для нажатия.

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

Пример:
Input: n = 1, presses = 1
Output: 2
Explanation: Status can be:
- [off] by pressing button 1
- [on] by pressing button 2


👨‍💻 Алгоритм:

1⃣Рассчитаем возможные множества остатков: то есть какие множества c_i = f_i (mod 2) возможны.

2⃣Так как c_i ≡ f_i и c_i ≤ f_i, если ∑f_i ≠ ∑c_i, или если ∑f_i < ∑c_i, это невозможно. В противном случае это возможно простым построением: выполните операции, указанные c_i, затем выполните операцию номер 1 с четным числом оставшихся операций.

3⃣Для каждого возможного множества остатков симулируем и запоминаем, как будут выглядеть первые 6 лампочек, сохраняя это в структуре Set. В конце возвращаем размер этого множества.

😎 Решение:
public class Solution {
public int FlipLights(int n, int m) {
var seen = new HashSet<int>();
n = Math.Min(n, 6);
int shift = Math.Max(0, 6 - n);
for (int cand = 0; cand < 16; ++cand) {
int bcount = Convert.ToString(cand, 2).Count(c => c == '1');
if (bcount % 2 == m % 2 && bcount <= m) {
int lights = 0;
if (((cand >> 0) & 1) > 0) lights ^= 0b111111 >> shift;
if (((cand >> 1) & 1) > 0) lights ^= 0b010101 >> shift;
if (((cand >> 2) & 1) > 0) lights ^= 0b101010 >> shift;
if (((cand >> 3) & 1) > 0) lights ^= 0b100100 >> shift;
seen.Add(lights);
}
}
return seen.Count;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1539. Kth Missing Positive Number
Сложность: easy

Дан массив arr из положительных целых чисел, отсортированных в строго возрастающем порядке, и целое число k.

Верните k-й положительный целочисленный элемент, который отсутствует в этом массиве.

Пример:
Input: arr = [2,3,4,7,11], k = 5
Output: 9
Explanation: The missing positive integers are [1,5,6,8,9,10,12,13,...]. The 5th missing positive integer is 9.


👨‍💻 Алгоритм:

1⃣Проверьте, является ли k-й отсутствующий номер меньше первого элемента массива. Если это так, верните k. Уменьшите k на количество положительных чисел, отсутствующих до начала массива: k -= arr[0] - 1.

2⃣Итерируйтесь по элементам массива. На каждом шаге вычисляйте количество отсутствующих положительных чисел между i+1-м и i-м элементами: currMissing = arr[i + 1] - arr[i] - 1. Сравните k с currMissing. Если k <= currMissing, то число для возврата находится между arr[i + 1] и arr[i], и вы можете его вернуть: arr[i] + k. В противном случае уменьшите k на currMissing и продолжайте.

3⃣Если элемент, который нужно вернуть, больше последнего элемента массива, верните его: arr[n - 1] + k.

😎 Решение:
public class Solution {
public int FindKthPositive(int[] arr, int k) {
if (k <= arr[0] - 1) {
return k;
}
k -= arr[0] - 1;
int n = arr.Length;
for (int i = 0; i < n - 1; ++i) {
int currMissing = arr[i + 1] - arr[i] - 1;
if (k <= currMissing) {
return arr[i] + k;
}
k -= currMissing;
}
return arr[n - 1] + k;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 1208. Get Equal Substrings Within Budget
Сложность: medium

Вам даны две строки s и t одинаковой длины и целое число maxCost.
Вы хотите преобразовать s в t. Изменение i-го символа строки s на i-й символ строки t стоит |s[i] - t[i]| (т.е. абсолютная разница между значениями ASCII символов).

Верните максимальную длину подстроки s, которую можно изменить, чтобы она соответствовала соответствующей подстроке t с затратами, не превышающими maxCost. Если нет подстроки из s, которую можно изменить на соответствующую подстроку из t, верните 0.

Пример:
Input: s = "abcd", t = "bcdf", maxCost = 3
Output: 3
Explanation: "abc" of s can change to "bcd".
That costs 3, so the maximum length is 3.


👨‍💻 Алгоритм:

1⃣Инициализация переменных:
maxLen для хранения максимальной длины подстроки с затратами, не превышающими maxCost.
start для хранения начального индекса текущей подстроки.
currCost для хранения текущих затрат на преобразование подстроки s в t.

2⃣Итерация по индексам от 0 до N-1:
Добавить текущие затраты на преобразование символа s[i] в t[i] к currCost.
Удалять элементы с левого конца, уменьшая затраты до тех пор, пока currCost не станет меньше или равным maxCost.
Обновить maxLen длиной текущей подстроки.

3⃣Возврат maxLen как результата.

😎 Решение:
public class Solution {
public int EqualSubstring(string s, string t, int maxCost) {
int N = s.Length;

int maxLen = 0;
int start = 0;
int currCost = 0;

for (int i = 0; i < N; i++) {
currCost += Math.Abs(s[i] - t[i]);

while (currCost > maxCost) {
currCost -= Math.Abs(s[start] - t[start]);
start++;
}

maxLen = Math.Max(maxLen, i - start + 1);
}

return maxLen;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Задача: 309. Best Time to Buy and Sell Stock with Cooldown
Сложность: medium

Дан массив prices, где prices[i] — цена данной акции в i-й день.

Найдите максимальную прибыль, которую можно получить. Вы можете совершить любое количество транзакций (т. е. купить и продать одну акцию несколько раз) с следующими ограничениями:

После продажи акции вы не можете покупать акции на следующий день (т. е. необходимо один день подождать).

Пример:
Input: prices = [1]
Output: 0


👨‍💻 Алгоритм:

1⃣Инициализация состояний
Используйте три состояния для отслеживания максимальной прибыли: hold: максимальная прибыль на данный день, если у вас есть акция. sold: максимальная прибыль на данный день, если вы продали акцию. cooldown: максимальная прибыль на данный день, если вы находитесь в периоде ожидания после продажи.

2⃣Обновление состояний
Итерируйте по каждому дню, обновляя состояния: hold: максимальная прибыль, если у вас есть акция на текущий день. sold: максимальная прибыль, если вы продаете акцию на текущий день. cooldown: максимальная прибыль, если вы находитесь в периоде ожидания на текущий день.

3⃣Определение максимальной прибыли
В конце итерации максимальная прибыль будет максимальным значением между состояниями sold и cooldown, так как hold состояние не может быть конечным состоянием для получения максимальной прибыли.

😎 Решение:
public class Solution {
public int MaxProfit(int[] prices) {
if (prices.Length == 0) return 0;

int hold = -prices[0];
int sold = 0;
int cooldown = 0;

for (int i = 1; i < prices.Length; i++) {
int newHold = Math.Max(hold, cooldown - prices[i]);
int newSold = hold + prices[i];
int newCooldown = Math.Max(cooldown, sold);

hold = newHold;
sold = newSold;
cooldown = newCooldown;
}

return Math.Max(sold, cooldown);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 655. Print Binary Tree
Сложность: medium

Учитывая корень двоичного дерева, постройте строковую матрицу res с индексом 0 размером m x n, которая представляет собой форматированную раскладку дерева. Форматированная матрица должна быть построена по следующим правилам: высота дерева равна height, количество строк m должно быть равно height + 1. Количество столбцов n должно быть равно 2height+1 - 1. Поместите корневой узел в середину верхней строки (более формально, в позицию res[0][(n-1)/2]).
Для каждого узла, который был помещен в матрицу в позицию res[r][c], поместите его левого ребенка в res[r+1][c-2height-r-1], а правого - в res[r+1][c+2height-r-1]. Продолжайте этот процесс, пока не будут размещены все узлы дерева. Любые пустые ячейки должны содержать пустую строку "". Верните построенную матрицу res.

Пример:
Input: root = [1,2]
Output:
[["","1",""],
["2","",""]]


👨‍💻 Алгоритм:

1⃣Найдите высоту дерева и определите размер матрицы (m x n).

2⃣Рекурсивно разместите узлы в матрице, начиная с корневого узла.

3⃣Верните заполненную матрицу.

😎 Решение:
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}

public class Solution {
private int FindHeight(TreeNode root) {
if (root == null) return -1;
return 1 + Math.Max(FindHeight(root.left), FindHeight(root.right));
}

private void Fill(string[,] res, TreeNode root, int r, int c, int height) {
if (root == null) return;
res[r, c] = root.val.ToString();
if (root.left != null) {
Fill(res, root.left, r + 1, c - (1 << (height - r - 1)), height);
}
if (root.right != null) {
Fill(res, root.right, r + 1, c + (1 << (height - r - 1)), height);
}
}

public IList<IList<string>> PrintTree(TreeNode root) {
int height = FindHeight(root);
int m = height + 1;
int n = (1 << (height + 1)) - 1;
string[,] res = new string[m, n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
res[i, j] = "";
}
}
Fill(res, root, 0, (n - 1) / 2, height);
IList<IList<string>> result = new List<IList<string>>();
for (int i = 0; i < m; i++) {
IList<string> row = new List<string>();
for (int j = 0; j < n; j++) {
row.Add(res[i, j]);
}
result.Add(row);
}
return result;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 90. Subsets II
Сложность: medium

Дан массив целых чисел nums, который может содержать повторяющиеся элементы. Верните все возможные подмножества (степенной набор).

Результирующий набор не должен содержать дублирующихся подмножеств. Возвращаемый результат может быть в любом порядке.

Пример:
Input: nums = [1,2,2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]


👨‍💻 Алгоритм:

1⃣Отсортируйте массив nums, чтобы элементы совпадали, шли подряд — это упростит проверку на дубликаты.

2⃣Сгенерируйте все возможные подмножества с помощью битовой маски. Для каждого из них subsetIndexпроверьте 0выбранные 2^n - 1биты и укажите подмножество.

3⃣Используйте HashSet, чтобы записывать отдельные подмножества по строковому представлению. Это предотвращает добавление дубликатов в результат.

😎 Решение:
public class Solution {
public IList<IList<int>> SubsetsWithDup(int[] nums) {
IList<IList<int>> subsets = new List<IList<int>>();
int n = nums.Length;
Array.Sort(nums);
int maxNumberOfSubsets = (int)Math.Pow(2, n);
HashSet<string> seen = new HashSet<string>();
for (int subsetIndex = 0; subsetIndex < maxNumberOfSubsets; subsetIndex++) {
List<int> currentSubset = new List<int>();
StringBuilder hashcode = new StringBuilder();
for (int j = 0; j < n; j++) {
int mask = 1 << j;
int isSet = mask & subsetIndex;
if (isSet != 0) {
currentSubset.Add(nums[j]);
hashcode.Append(nums[j]).Append(",");
}
}

if (!seen.Contains(hashcode.ToString())) {
seen.Add(hashcode.ToString());
subsets.Add(currentSubset);
}
}

return subsets;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 174. Dungeon Game
Сложность: hard

Демоны захватили принцессу и заточили её в правом нижнем углу подземелья. Подземелье состоит из m x n комнат, расположенных в виде двумерной сетки. Наш отважный рыцарь изначально находится в левом верхнем углу и должен пробиться через подземелье, чтобы спасти принцессу.

У рыцаря есть начальное количество очков здоровья, представленное положительным целым числом. Если в какой-то момент его очки здоровья упадут до 0 или ниже, он немедленно умрёт.

Некоторые комнаты охраняются демонами (представлены отрицательными числами), поэтому рыцарь теряет здоровье, заходя в эти комнаты; другие комнаты либо пусты (представлены как 0), либо содержат магические шары, увеличивающие здоровье рыцаря (представлены положительными числами).

Чтобы как можно быстрее добраться до принцессы, рыцарь решает двигаться только вправо или вниз на каждом шаге.

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

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

Пример:
Input: dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
Output: 7
Explanation: The initial health of the knight must be at least 7 if he follows the optimal path: RIGHT-> RIGHT -> DOWN -> DOWN.


👨‍💻 Алгоритм:

1⃣Определяем матрицу dp[row][col], где элемент dp[row][col] указывает минимальное количество очков здоровья, которое потребуется рыцарю, начиная с соответствующей ячейки подземелья dungeon[row][col], чтобы добраться до цели.

2⃣Для расчета значений матрицы dp начинаем с правого нижнего угла подземелья и идем в порядке справа-налево и снизу-вверх. Для каждой ячейки подземелья рассчитываем соответствующее значение dp[row][col].

3⃣Значение dp[row][col] определяется следующими условиями:
Если возможно, сделав шаг вправо из текущей ячейки подземелья, рыцарь может нуждаться в right_health очках здоровья.
Если возможно, сделав шаг вниз из текущей ячейки подземелья, рыцарь может нуждаться в down_health очках здоровья.
Если существует хотя бы одна из вышеуказанных альтернатив, берём минимальное значение из них для dp[row][col].
Если ни одна из вышеуказанных альтернатив не существует, то есть мы находимся в целевой ячейке, возможны два случая:
Если текущая ячейка содержит магический шар, то достаточно одного очка здоровья.
Если текущая ячейка содержит демона, то рыцарь должен иметь одно очко здоровья плюс очки урона, которые нанесет демон.

😎 Решение:
public class Solution {
const int inf = int.MaxValue;
int[,] dp;
int rows, cols;

public int GetMinHealth(int currCell, int nextRow, int nextCol) {
if (nextRow >= this.rows || nextCol >= this.cols)
return inf;
int nextCell = this.dp[nextRow, nextCol];
return Math.Max(1, nextCell - currCell);
}

public int CalculateMinimumHP(int[][] dungeon) {
this.rows = dungeon.Length;
this.cols = dungeon[0].Length;
this.dp = new int[rows, cols];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dp[i, j] = inf;
}
}

int currCell, rightHealth, downHealth, nextHealth, minHealth;
for (int row = this.rows - 1; row >= 0; --row) {
for (int col = this.cols - 1; col >= 0; --col) {
currCell = dungeon[row][col];

rightHealth = GetMinHealth(currCell, row, col + 1);
downHealth = GetMinHealth(currCell, row + 1, col);
nextHealth = Math.Min(rightHealth, downHealth);

if (nextHealth != inf) {
minHealth = nextHealth;
} else {
minHealth = currCell >= 0 ? 1 : 1 - currCell;
}

this.dp[row, col] = minHealth;
}
}

return this.dp[0, 0];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 422. Valid Word Square
Сложность: easy

Дан массив строк words, верните true, если он образует правильный квадрат слов.

Последовательность строк образует правильный квадрат слов, если k-я строка и k-й столбец читаются одинаково, где 0 <= k < max(numRows, numColumns).

Пример:
Input: words = ["abcd","bnrt","crmy","dtye"]
Output: true
Explanation:
The 1st row and 1st column both read "abcd".
The 2nd row and 2nd column both read "bnrt".
The 3rd row and 3rd column both read "crmy".
The 4th row and 4th column both read "dtye".
Therefore, it is a valid word square.


👨‍💻 Алгоритм:

1⃣Инициализируйте переменные: cols для максимальной длины слов в массиве, rows для количества строк в массиве words, и пустой массив newWords для хранения новых слов, представленных каждым столбцом.

2⃣Итерация по массиву words, определение максимальной длины слова для cols, проверка, что количество строк равно количеству столбцов. Если условие не выполняется, возвращаем false.

3⃣Для каждого столбца col от 0 до cols - 1, формируем строку newWord из символов на позиции (row, col) для каждой строки. Сохраняем newWord в массиве newWords. В конце, если newWords и words равны, возвращаем true, иначе false.

😎 Решение:
using System;
using System.Collections.Generic;

public class Solution {
public bool ValidWordSquare(IList<string> words) {
int cols = 0;
int rows = words.Count;
var newWords = new List<string>();

foreach (var word in words) {
cols = Math.Max(cols, word.Length);
}

if (cols != words[0].Length || rows != cols) {
return false;
}

for (int col = 0; col < cols; ++col) {
var newWord = string.Empty;
for (int row = 0; row < rows; ++row) {
if (col < words[row].Length) {
newWord += words[row][col];
}
}
newWords.Add(newWord);
}

return words.SequenceEqual(newWords);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 712. Minimum ASCII Delete Sum for Two Strings
Сложность: medium

Если даны две строки s1 и s2, верните наименьшую ASCII-сумму удаленных символов, чтобы сделать две строки равными.

Пример:
Input: s1 = "sea", s2 = "eat"
Output: 231


👨‍💻 Алгоритм:

1⃣Создайте двумерный массив dp размером (len(s1) + 1) x (len(s2) + 1), где dp[i][j] будет хранить наименьшую ASCII-сумму удаленных символов для первых i символов s1 и первых j символов s2.

2⃣Заполните первую строку и первый столбец массива dp суммами ASCII значений символов, которые необходимо удалить для достижения пустой строки.

3⃣Заполните оставшуюся часть массива dp следующим образом: Если символы s1[i-1] и s2[j-1] равны, dp[i][j] = dp[i-1][j-1]. Иначе, dp[i][j] = min(dp[i-1][j] + ord(s1[i-1]), dp[i][j-1] + ord(s2[j-1])).

😎 Решение:
public class Solution {
public int MinimumDeleteSum(string s1, string s2) {
int[][] dp = new int[s1.Length + 1][];
for (int i = 0; i < dp.Length; i++) {
dp[i] = new int[s2.Length + 1];
}
for (int i = 1; i <= s1.Length; i++) {
dp[i][0] = dp[i - 1][0] + s1[i - 1];
}
for (int j = 1; j <= s2.Length; j++) {
dp[0][j] = dp[0][j - 1] + s2[j - 1];
}
for (int i = 1; i <= s1.Length; i++) {
for (int j = 1; j <= s2.Length; j++) {
if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.Min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1]);
}
}
}
return dp[s1.Length][s2.Length];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 236. Lowest Common Ancestor of a Binary Tree
Сложность: medium

Дано бинарное дерево. Найдите наименьший общий предок (LCA) двух заданных узлов в дереве.

Согласно определению LCA на Википедии: "Наименьший общий предок определяется между двумя узлами p и q как наименьший узел в дереве T, который имеет как p, так и q в качестве потомков (где мы допускаем, что узел может быть потомком самого себя)."

Пример:
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
Output: 3
Explanation: The LCA of nodes 5 and 1 is 3.


👨‍💻 Алгоритм:

1⃣Начало обхода дерева с корня: Начните обход дерева с корневого узла. Если текущий узел является одним из узлов p или q, установите переменную mid в значение True и продолжите поиск другого узла в левой и правой ветвях.

2⃣Проверка поддеревьев: Выполните рекурсивный обход левой и правой ветвей дерева. Если какая-либо из ветвей (левая или правая) возвращает True, это означает, что один из двух узлов найден ниже по дереву.

3⃣Определение LCA: Если в любой момент обхода дерева две из трех переменных (left, right или mid) становятся True, это означает, что найден наименьший общий предок (LCA) для узлов p и q.

😎 Решение:
public class Solution {
private TreeNode ans;

public Solution() {
this.ans = null;
}

private bool RecurseTree(TreeNode currentNode, TreeNode p, TreeNode q) {
if (currentNode == null) {
return false;
}

int left = this.RecurseTree(currentNode.left, p, q) ? 1 : 0;
int right = this.RecurseTree(currentNode.right, p, q) ? 1 : 0;
int mid = (currentNode == p || currentNode == q) ? 1 : 0;

if (mid + left + right >= 2) {
this.ans = currentNode;
}

return (mid + left + right > 0);
}

public TreeNode LowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.RecurseTree(root, p, q);
return this.ans;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊1
Задача: 680. Valid Palindrome II
Сложность: easy

Дана строка s, вернуть true, если s может быть палиндромом после удаления не более одного символа из нее.

Пример:
Input: s = "aba"
Output: true


👨‍💻 Алгоритм:

1⃣Создайте вспомогательную функцию checkPalindrome, которая принимает строку s и два указателя i и j. Эта функция возвращает логическое значение, указывающее, является ли подстрока s.substring(i, j) палиндромом.

2⃣Инициализируйте два указателя: i = 0 и j = s.length() - 1. Пока i < j, проверьте, совпадают ли символы в индексах i и j. Если нет, это значит, что нам нужно удалить один из этих символов.

3⃣Попробуйте оба варианта, используя checkPalindrome. Верните true, если либо checkPalindrome(s, i, j - 1), либо checkPalindrome(s, i + 1, j) возвращает true. Если мы выходим из цикла while, это значит, что исходная строка является палиндромом. Поскольку нам не нужно было использовать удаление, следует вернуть true.

😎 Решение:
public class Solution {
private bool CheckPalindrome(string s, int i, int j) {
while (i < j) {
if (s[i] != s[j]) {
return false;
}
i++;
j--;
}
return true;
}

public bool ValidPalindrome(string s) {
int i = 0;
int j = s.Length - 1;

while (i < j) {
if (s[i] != s[j]) {
return CheckPalindrome(s, i, j - 1) || CheckPalindrome(s, i + 1, j);
}
i++;
j--;
}

return true;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Задача: 740. Delete and Earn
Сложность: medium

Вам дан целочисленный массив nums. Вы хотите максимизировать количество очков, выполнив следующую операцию любое количество раз: Выберите любой элемент nums[i] и удалите его, чтобы заработать nums[i] очков. После этого вы должны удалить каждый элемент, равный nums[i] - 1, и каждый элемент, равный nums[i] + 1. Верните максимальное количество очков, которое вы можете заработать, применив вышеуказанную операцию некоторое количество раз.

Пример:
Input: nums = [3,4,2]
Output: 6


👨‍💻 Алгоритм:

1⃣Подсчитайте количество каждого числа в массиве nums.

2⃣Используйте динамическое программирование для расчета максимальных очков, которые можно заработать, используя накопленный результат для чисел, меньших текущего. Добавьте текущий день в стек.

3⃣Для каждого числа num в nums, учитывайте два случая: не брать число или взять число и добавить его очки.

😎 Решение:
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution {
public int DeleteAndEarn(int[] nums) {
var count = new Dictionary<int, int>();
foreach (var num in nums) {
if (!count.ContainsKey(num)) {
count[num] = 0;
}
count[num]++;
}

int avoid = 0, using = 0, prev = -1;
foreach (var num in count.Keys.OrderBy(x => x)) {
if (num - 1 != prev) {
int newAvoid = Math.Max(avoid, using);
using = num * count[num] + Math.Max(avoid, using);
avoid = newAvoid;
} else {
int newAvoid = Math.Max(avoid, using);
using = num * count[num] + avoid;
avoid = newAvoid;
}
prev = num;
}

return Math.Max(avoid, using);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM