JavaScript | LeetCode
8.82K subscribers
243 photos
1.3K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.me/+T0COHtFzCJkwMDUy
Вопросы собесов t.me/+kXKgJEjRUww3N2Ni
Вакансии t.me/+CgCAzIyGHHg0Nzky
Download Telegram
Задача: 1037. Valid Boomerang
Сложность: easy

Если задан массив points, где points[i] = [xi, yi] представляет точку на плоскости X-Y, верните true, если эти точки являются бумерангом. Бумеранг - это набор из трех точек, которые отличаются друг от друга и не являются прямой линией.

Пример:
Input: blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]
Output: false


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

1⃣Проверка уникальности точек:
Убедитесь, что все три точки уникальны. Если любые две точки совпадают, то это не бумеранг.

2⃣Проверка на коллинеарность:
Используйте определитель (или площадь параллелограмма) для проверки, находятся ли три точки на одной прямой. Если площадь параллелограмма, образованного тремя точками, равна нулю, то точки коллинеарны.

3⃣Результат:
Если точки уникальны и не коллинеарны, верните true. В противном случае, верните false.

😎 Решение:
var isBoomerang = function(points) {
let [x1, y1] = points[0];
let [x2, y2] = points[1];
let [x3, y3] = points[2];
return (x1 !== x2 || y1 !== y2) &&
(x1 !== x3 || y1 !== y3) &&
(x2 !== x3 || y2 !== y3) &&
(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) !== 0;
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 779. K-th Symbol in Grammar
Сложность: medium

Мы строим таблицу из n строк (индексация начинается с 1). Начинаем с написания 0 в первой строке. Теперь в каждой следующей строке мы смотрим на предыдущую строку и заменяем каждое появление 0 на 01, и каждое появление 1 на 10.

Например, для n = 3, первая строка будет 0, вторая строка будет 01, и третья строка будет 0110.
Даны два целых числа n и k, вернуть k-й (индексация начинается с 1) символ в n-й строке таблицы из n строк.

Пример:
Input: n = 1, k = 1
Output: 0
Explanation: row 1: 0


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

1⃣Создайте метод depthFirstSearch, который принимает n количество строк в текущем дереве, k позицию целевого узла в последней строке и rootVal значение корня текущего дерева в качестве параметров. Если n равно 1, то в нашем дереве будет единственный узел, и этот узел является целевым узлом. Поэтому возвращаем его значение rootVal.

2⃣Найдите количество узлов в последней строке текущего дерева, totalNodes, которое равно 2^(n-1). Если текущий целевой узел k находится в левой половине последней строки текущего поддерева (то есть k <= totalNodes / 2), переходим в левое поддерево. Если значение текущего узла rootVal равно 0, то значение следующего узла будет 0, иначе следующее значение узла будет 1. Возвращаем вызов depthFirstSearch(n - 1, k, nextRootVal).

3⃣В противном случае, если текущий целевой узел k находится в правой половине последней строки текущего поддерева (то есть k > totalNodes / 2), переходим в правое поддерево. Если значение текущего узла rootVal равно 0, то значение следующего узла будет 1, иначе следующее значение узла будет 0. Кроме того, позиция целевого узла изменится на (k - (totalNodes / 2)). Возвращаем вызов depthFirstSearch(n - 1, newPosition, nextRootVal).

😎 Решение:
class Solution {
depthFirstSearch(n, k, rootVal) {
if (n === 1) return rootVal;
const totalNodes = 1 << (n - 1);
if (k > totalNodes / 2) {
const nextRootVal = rootVal === 0 ? 1 : 0;
return this.depthFirstSearch(n - 1, k - totalNodes / 2, nextRootVal);
} else {
const nextRootVal = rootVal === 0 ? 0 : 1;
return this.depthFirstSearch(n - 1, k, nextRootVal);
}
}

kthGrammar(n, k) {
return this.depthFirstSearch(n, k, 0);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1013. Partition Array Into Three Parts With Equal Sum
Сложность: easy

Если задан массив целых чисел arr, верните true, если мы можем разбить массив на три непустые части с равными суммами. Формально, мы можем разбить массив, если можем найти индексы i + 1 < j с (arr[0] + arr[1] + ... + arr[i] == arr[i + 1] + arr[i + 2] + ... + arr[j - 1] == arr[j] + arr[j + 1] + ... + arr[arr.length - 1])

Пример:
Input: arr = [0,2,1,-6,6,-7,9,1,2,0,1]
Output: true


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

1⃣Вычисление общей суммы:
Вычислите общую сумму всех элементов массива. Если эта сумма не делится на 3 без остатка, вернуть false, так как невозможно разбить массив на три части с равной суммой.

2⃣Поиск первой и второй части:
Итерируйте по массиву и ищите первую часть с суммой, равной одной трети от общей суммы. Продолжайте итерацию для поиска второй части с такой же суммой.
Убедитесь, что между первой и второй частью есть хотя бы один элемент.

3⃣Проверка третьей части:
Убедитесь, что оставшаяся часть массива также имеет ту же сумму, что и две найденные части. Если да, вернуть true, иначе false.

😎 Решение:
class Solution {
canThreePartsEqualSum(arr) {
const totalSum = arr.reduce((sum, num) => sum + num, 0);
if (totalSum % 3 !== 0) {
return false;
}

const target = totalSum / 3;
let partSum = 0, count = 0;

for (let i = 0; i < arr.length; i++) {
partSum += arr[i];
if (partSum === target) {
count++;
partSum = 0;
if (count === 2 && i < arr.length - 1) {
return true;
}
}
}

return false;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 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⃣Верните заполненную матрицу.

😎 Решение:
function TreeNode(val, left, right) {
this.val = (val===undefined ? 0 : val)
this.left = (left===undefined ? null : left)
this.right = (right===undefined ? null : right)
}

const findHeight = (root) => {
if (!root) return -1;
return 1 + Math.max(findHeight(root.left), findHeight(root.right));
}

const fill = (res, root, r, c, height) => {
if (!root) return;
res[r][c] = root.val.toString();
if (root.left) {
fill(res, root.left, r + 1, c - (1 << (height - r - 1)), height);
}
if (root.right) {
fill(res, root.right, r + 1, c + (1 << (height - r - 1)), height);
}
}

const printTree = (root) => {
const height = findHeight(root);
const m = height + 1;
const n = (1 << (height + 1)) - 1;
const res = Array.from({ length: m }, () => Array(n).fill(""));
fill(res, root, 0, Math.floor((n - 1) / 2), height);
return res;
}


Ставь 👍 и забирай 📚 Базу знаний
👍1
Задача: 1062. Longest Repeating Substring
Сложность: medium

Дана строка s. Вернуть длину самой длинной повторяющейся подстроки. Если повторяющаяся подстрока отсутствует, вернуть 0.

Пример:
Input: s = "abcd"
Output: 0
Explanation: There is no repeating substring.


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

1⃣Перемещайте скользящее окно длиной L по строке длиной N.

2⃣Проверьте, находится ли строка в скользящем окне в хэш-наборе уже виденных строк. Если да, то повторяющаяся подстрока находится здесь. Если нет, сохраните строку из скользящего окна в хэш-наборе.

3⃣Очевидный недостаток этого подхода — большое потребление памяти в случае длинных строк.

😎 Решение:
class Solution {
search(L, n, S) {
const seen = new Set();
for (let start = 0; start <= n - L; ++start) {
const tmp = S.substring(start, start + L);
if (seen.has(tmp)) return start;
seen.add(tmp);
}
return -1;
}

longestRepeatingSubstring(S) {
const n = S.length;
let left = 1, right = n;
while (left <= right) {
const L = left + Math.floor((right - left) / 2);
if (this.search(L, n, S) !== -1) {
left = L + 1;
} else {
right = L - 1;
}
}
return left - 1;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 865. Smallest Subtree with all the Deepest Nodes
Сложность: medium

Дан корень бинарного дерева, глубина каждого узла — это кратчайшее расстояние до корня.

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

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

Поддерево узла — это дерево, состоящее из этого узла и всех его потомков.

Пример:
Input: root = [3,5,1,6,2,0,8,null,null,7,4]
Output: [2,7,4]
Explanation: We return the node with value 2, colored in yellow in the diagram.
The nodes coloured in blue are the deepest nodes of the tree.
Notice that nodes 5, 3 and 2 contain the deepest nodes in the tree but node 2 is the smallest subtree among them, so we return it.


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

1⃣В первой фазе используем поиск в глубину (DFS), чтобы аннотировать узлы. Каждый узел будет хранить информацию о своей глубине и о самой большой глубине среди его потомков.

2⃣Во второй фазе также используем DFS для функции answer(node), которая возвращает наименьшее поддерево, содержащее все самые глубокие узлы. Функция сравнивает глубины левых и правых поддеревьев узла для определения наименьшего поддерева.

3⃣Функция answer(node) возвращает поддерево, которое содержит все самые глубокие узлы всего дерева, а не только рассматриваемого поддерева.

😎 Решение:
var subtreeWithAllDeepest = function(root) {
const depth = new Map([[null, -1]]);
const dfs = (node, parent) => {
if (node) {
depth.set(node, depth.get(parent) + 1);
dfs(node.left, node);
dfs(node.right, node);
}
};
dfs(root, null);
const maxDepth = Math.max(...depth.values());

const answer = (node) => {
if (!node || depth.get(node) === maxDepth) return node;
const L = answer(node.left);
const R = answer(node.right);
return L && R ? node : L || R;
};

return answer(root);
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 621. Task Scheduler
Сложность: medium

Вам дан массив задач процессора, каждая из которых представлена буквами от A до Z, и время охлаждения, n. Каждый цикл или интервал позволяет завершить одну задачу. Задачи могут быть выполнены в любом порядке, но есть ограничение: одинаковые задачи должны быть разделены не менее чем n интервалами из-за времени охлаждения. Верните минимальное количество интервалов, необходимое для выполнения всех задач.

Пример:
Input: tasks = ["A","A","A","B","B","B"], n = 2
Output: 8


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

1⃣Подсчитайте количество каждой задачи и найдите максимальное количество вхождений (maxFreq).

2⃣Вычислите количество интервалов, необходимых для задач с maxFreq: (maxFreq - 1) * (n + 1) + countMaxFreq, где countMaxFreq - количество задач, имеющих maxFreq.

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

😎 Решение:
function leastInterval(tasks, n) {
const taskCounts = {};
for (const task of tasks) {
taskCounts[task] = (taskCounts[task] || 0) + 1;
}

const maxFreq = Math.max(...Object.values(taskCounts));
const countMaxFreq = Object.values(taskCounts).filter(count => count === maxFreq).length;

return Math.max(tasks.length, (maxFreq - 1) * (n + 1) + countMaxFreq);
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 834. Sum of Distances in Tree
Сложность: hard

Есть неориентированное связное дерево с n узлами, пронумерованными от 0 до n - 1, и n - 1 ребрами.

Вам даны целое число n и массив edges, где edges[i] = [ai, bi] указывает, что существует ребро между узлами ai и bi в дереве.

Верните массив answer длиной n, где answer[i] — это сумма расстояний между i-м узлом в дереве и всеми другими узлами.

Пример:
Input: n = 6, edges = [[0,1],[0,2],[2,3],[2,4],[2,5]]
Output: [8,12,6,10,10,10]
Explanation: The tree is shown above.
We can see that dist(0,1) + dist(0,2) + dist(0,3) + dist(0,4) + dist(0,5)
equals 1 + 1 + 2 + 2 + 2 = 8.
Hence, answer[0] = 8, and so on.


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

1⃣Постройте дерево и выполните обход в глубину (DFS) для расчета количества узлов в поддереве и суммы расстояний до всех узлов поддерева.

2⃣На основе полученных данных рассчитайте сумму расстояний для корня дерева.

3⃣Выполните второй обход в глубину (DFS) для корректировки суммы расстояний для каждого узла на основе суммы расстояний его родительского узла.

😎 Решение:
var sumOfDistancesInTree = function(N, edges) {
const graph = Array.from({ length: N }, () => new Set());
for (const [u, v] of edges) {
graph[u].add(v);
graph[v].add(u);
}

const ans = Array(N).fill(0);
const count = Array(N).fill(1);

const dfs = (node, parent) => {
for (const child of graph[node]) {
if (child !== parent) {
dfs(child, node);
count[node] += count[child];
ans[node] += ans[child] + count[child];
}
}
};

const dfs2 = (node, parent) => {
for (const child of graph[node]) {
if (child !== parent) {
ans[child] = ans[node] - count[child] + N - count[child];
dfs2(child, node);
}
}
};

dfs(0, -1);
dfs2(0, -1);
return ans;


Ставь 👍 и забирай 📚 Базу знаний
Задача: 231. Power of Two
Сложность: easy

Дано целое число n, верните true, если оно является степенью двойки. В противном случае верните false.
Целое число n является степенью двойки, если существует целое число x, такое что n == 2^x.

Пример:
Input: n = 1
Output: true
Explanation: 2^0 = 1


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

1⃣Проверка на ноль: Если n равно нулю, верните false, так как ноль не является степенью двойки.

2⃣Преобразование к длинному типу: Преобразуйте n к типу long, чтобы избежать переполнения при выполнении побитовых операций.

3⃣Побитовая проверка: Используйте побитовую операцию, чтобы проверить, является ли число степенью двойки. Число является степенью двойки, если результат выражения (x & (-x)) равен x.

😎 Решение:
class Solution {
isPowerOfTwo(n) {
if (n === 0) return false
let x = BigInt(n)
return (x & -x) === x
}
}


Ставь 👍 и забирай 📚 Базу знаний
👍1
Задача: 461. Hamming Distance
Сложность: easy

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

Пример:
Input: x = 3, y = 1
Output: 1


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

1⃣Во-первых, стоит упомянуть, что в большинстве (или, по крайней мере, во многих) языков программирования есть встроенные функции для подсчета битов, установленных в 1. Если вам нужно решить такую задачу в реальном проекте, то лучше использовать эти функции, чем изобретать велосипед.

2⃣Однако, поскольку это задача на LeetCode, использование встроенных функций можно сравнить с "реализацией LinkedList с использованием LinkedList". Поэтому рассмотрим также несколько интересных ручных алгоритмов для подсчета битов.

3⃣Пошаговый подсчет битов:
Выполните побитовое XOR между x и y.
Инициализируйте счетчик bitCount = 0.
Пока число не равно нулю:
Если текущий бит равен 1, увеличьте bitCount.
Сдвиньте число вправо на один бит.
Возвращайте bitCount.

😎 Решение:
class Solution {
hammingDistance(x, y) {
return (x ^ y).toString(2).split('0').join('').length;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1006. Clumsy Factorial
Сложность: medium

Факториал целого положительного числа n - это произведение всех целых положительных чисел, меньших или равных n. Например, факториал(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1.
Мы составляем неуклюжий факториал, используя целые числа в порядке убывания, заменяя операции умножения на фиксированную последовательность операций с умножением "*", делением "/", сложением "+" и вычитанием "-" в этом порядке. Например, clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1. Однако эти операции по-прежнему применяются с использованием обычного порядка операций арифметики. Мы выполняем все шаги умножения и деления перед шагами сложения и вычитания, а шаги умножения и деления выполняются слева направо. Кроме того, деление, которое мы используем, является делением с полом, так что 10 * 9 / 8 = 90 / 8 = 11. Учитывая целое число n, верните неуклюжий факториал n.

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


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

1⃣Инициализация переменных и обработка первых трех чисел:
Создайте переменные для хранения результата и текущего значения.
Если n меньше или равен 3, обработайте случай отдельно, выполняя операции в порядке убывания, и верните результат.

2⃣Выполнение операций в цикле:
Создайте цикл, который будет обрабатывать числа от n до 1 в порядке убывания.
В цикле выполняйте операции *, /, +, и - последовательно.
Обновляйте текущий результат на каждом шаге в зависимости от остатка от деления текущего индекса на 4.

3⃣Учет оставшихся операций и возврат результата:
После завершения цикла добавьте или вычтите оставшиеся числа (если есть) к результату.
Верните окончательный результат.

😎 Решение:
class Solution {
clumsy(n) {
if (n == 0) return 0;
if (n == 1) return 1;
if (n == 2) return 2 * 1;
if (n == 3) return 3 * 2 / 1;

let res = n * (n - 1) / (n - 2);
n -= 3;
if (n > 0) res += n--;

while (n > 0) {
res -= n * (n - 1) / (n - 2);
n -= 3;
if (n > 0) res += n--;
}

return res;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 520. Detect Capital
Сложность: easy

Мы определяем правильное использование заглавных букв в слове, когда выполняется одно из следующих условий:
Все буквы в этом слове заглавные, как "USA".
Все буквы в этом слове строчные, как "leetcode".
Только первая буква в этом слове заглавная, как "Google".
Дана строка word. Верните true, если использование заглавных букв в ней правильное.

Пример:
Input: word = "USA"
Output: true


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

1⃣Шаблон для первого случая в регулярном выражении: [A-Z]*, где [A-Z] соответствует одной заглавной букве от 'A' до 'Z', а * означает повторение предыдущего шаблона 0 или более раз. Этот шаблон представляет "Все заглавные буквы".

2⃣Шаблон для второго случая в регулярном выражении: [a-z]*, где [a-z] соответствует одной строчной букве от 'a' до 'z'. Этот шаблон представляет "Все строчные буквы". Шаблон для третьего случая в регулярном выражении: [A-Z][a-z]*, где первая буква заглавная, а остальные строчные.

3⃣Объедините эти три шаблона: [A-Z]*|[a-z]*|[A-Z][a-z]*, где | означает "или". Мы можем объединить второй и третий случай, получив . [a-z]*, где . соответствует любому символу. Итоговый шаблон: [A-Z]*|.[a-z]*.

😎 Решение:
var detectCapitalUse = function(word) {
let pattern = /^[A-Z]*$|^[a-z]*$|^[A-Z][a-z]*$/;
return pattern.test(word);
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1426. Counting Elements
Сложность: easy

Дан целочисленный массив arr, посчитайте, сколько элементов x в нем есть таких, что x + 1 также находится в arr. Если в arr есть дубликаты, считайте их отдельно.

Пример:
Input: arr = [1,2,3]
Output: 2
Explanation: 1 and 2 are counted cause 2 and 3 are in arr.


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

1⃣Создайте вспомогательную функцию для проверки, содержится ли элемент в массиве.

2⃣Итерируйте по каждому элементу массива и используйте вспомогательную функцию для проверки, содержится ли элемент x + 1 в массиве.

3⃣Увеличьте счетчик, если x + 1 найден, и верните значение счетчика.

😎 Решение:
class Solution {
countElements(arr) {
let count = 0;
for (let x of arr) {
if (this.integerInArray(arr, x + 1)) {
count++;
}
}
return count;
}

integerInArray(arr, target) {
for (let x of arr) {
if (x === target) {
return true;
}
}
return false;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 756. Pyramid Transition Matrix
Сложность: medium

Вы складываете блоки так, чтобы получилась пирамида. Каждый блок имеет цвет, который представлен одной буквой. Каждый ряд блоков содержит на один блок меньше, чем ряд под ним, и располагается по центру сверху. Чтобы пирамида выглядела эстетично, допускаются только определенные треугольные узоры. Треугольный узор состоит из одного блока, уложенного поверх двух блоков. Шаблоны задаются в виде списка допустимых трехбуквенных строк, где первые два символа шаблона представляют левый и правый нижние блоки соответственно, а третий символ - верхний блок. Например, "ABC" представляет треугольный шаблон с блоком 'C', уложенным поверх блоков 'A' (слева) и 'B' (справа). Обратите внимание, что это отличается от "BAC", где "B" находится слева внизу, а "A" - справа внизу. Вы начинаете с нижнего ряда блоков bottom, заданного в виде одной строки, который вы должны использовать в качестве основания пирамиды. Учитывая bottom и allowed, верните true, если вы можете построить пирамиду до самой вершины так, чтобы каждый треугольный узор в пирамиде был в allowed, или false в противном случае.

Пример:
Input: bottom = "BCD", allowed = ["BCC","CDE","CEA","FFF"]
Output: true


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

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

2⃣Напишите рекурсивную функцию, которая проверяет возможность построения следующего уровня пирамиды.

3⃣Начните с нижнего уровня пирамиды и используйте рекурсию для построения каждого следующего уровня, проверяя каждый треугольный узор на допустимость.

😎 Решение:
var pyramidTransition = function(bottom, allowed) {
let allowedDict = {};

for (let pattern of allowed) {
let key = pattern.slice(0, 2);
if (!(key in allowedDict)) {
allowedDict[key] = [];
}
allowedDict[key].push(pattern[2]);
}

function canBuild(currentLevel) {
if (currentLevel.length == 1) return true;

let nextLevelOptions = [];
for (let i = 0; i < currentLevel.length - 1; i++) {
let key = currentLevel.slice(i, i + 2);
if (!(key in allowedDict)) return false;
nextLevelOptions.push(allowedDict[key]);
}

return canBuildNextLevel(nextLevelOptions, 0, "");
}

function canBuildNextLevel(options, index, nextLevel) {
if (index == options.length) return canBuild(nextLevel);
for (let option of options[index]) {
if (canBuildNextLevel(options, index + 1, nextLevel + option)) return true;
}
return false;
}

return canBuild(bottom);
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1203. Sort Items by Groups Respecting Dependencies
Сложность: hard

Есть n предметов, каждый из которых принадлежит нулевой или одной из m групп, где group[i] — это группа, к которой принадлежит i-й предмет, и равно -1, если i-й предмет не принадлежит никакой группе. Предметы и группы имеют индексацию с нуля. Группа может не иметь ни одного предмета.

Верните отсортированный список предметов таким образом:
Предметы, принадлежащие одной группе, расположены рядом друг с другом в отсортированном списке.
Существуют некоторые отношения между этими предметами, где beforeItems[i] — это список, содержащий все предметы, которые должны быть перед i-м предметом в отсортированном массиве (слева от i-го предмета).
Верните любое решение, если существует более одного решения, и верните пустой список, если решения не существует.

Пример:
Input: n = 8, m = 2, group = [-1,-1,1,0,0,1,0,-1], beforeItems = [[],[6],[5],[6],[3,6],[],[],[]]
Output: [6,3,4,1,5,2,0,7]


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

1⃣Инициализация и создание графов:
Присвоить уникальные идентификаторы группам для элементов без группы.
Создать два графа: item_graph для элементов и group_graph для групп. Также создать два массива для учета входящих рёбер для элементов и групп.

2⃣Построение графов:
Пройти по массиву beforeItems и добавить зависимости между элементами в item_graph, увеличивая счётчик входящих рёбер.
Если элементы принадлежат разным группам, добавить зависимость между группами в group_graph, увеличивая счётчик входящих рёбер.

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

😎 Решение:
var sortItems = function(n, m, group, beforeItems) {
let groupId = m;
for (let i = 0; i < n; i++) if (group[i] === -1) group[i] = groupId++;

const itemGraph = new Map();
const groupGraph = new Map();
const itemIndegree = Array(n).fill(0);
const groupIndegree = Array(groupId).fill(0);
for (let i = 0; i < n; i++) itemGraph.set(i, []);
for (let i = 0; i < groupId; i++) groupGraph.set(i, []);

for (let curr = 0; curr < n; curr++) {
for (const prev of beforeItems[curr]) {
itemGraph.get(prev).push(curr);
itemIndegree[curr]++;
if (group[curr] !== group[prev]) {
groupGraph.get(group[prev]).push(group[curr]);
groupIndegree[group[curr]]++;
}
}
}

const itemOrder = topologicalSort(itemGraph, itemIndegree);
const groupOrder = topologicalSort(groupGraph, groupIndegree);
if (itemOrder.length === 0 || groupOrder.length === 0) return [];

const orderedGroups = new Map();
for (const item of itemOrder) {
if (!orderedGroups.has(group[item])) orderedGroups.set(group[item], []);
orderedGroups.get(group[item]).push(item);
}

const answerList = [];
for (const groupIndex of groupOrder) {
if (orderedGroups.has(groupIndex)) answerList.push(...orderedGroups.get(groupIndex));
}

return answerList;
};

function topologicalSort(graph, indegree) {
const visited = [];
const stack = [];
for (const [key] of graph.entries()) if (indegree[key] === 0) stack.push(key);

while (stack.length > 0) {
const curr = stack.pop();
visited.push(curr);
for (const next of graph.get(curr)) {
indegree[next]--;
if (indegree[next] === 0) stack.push(next);
}
}

return visited.length === graph.size ? visited : [];
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 228. Summary Ranges
Сложность: easy

Вам дан отсортированный массив уникальных целых чисел nums.
Диапазон [a,b] - это множество всех целых чисел от a до b (включительно).
Верните наименьший отсортированный список диапазонов, которые покрывают все числа в массиве точно. То есть, каждый элемент nums покрывается ровно одним из диапазонов, и не существует такого целого числа x, чтобы x находился в одном из диапазонов, но не находился в nums.
Каждый диапазон [a,b] в списке должен быть представлен в формате:
"a->b" если a != b
"a" если a == b

Пример:
Input: nums = [0,1,2,4,5,7]
Output: ["0->2","4->5","7"]
Explanation: The ranges are:
[0,2] --> "0->2"
[4,5] --> "4->5"
[7,7] --> "7"


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

1⃣Создайте список строк ranges, который будет содержать решение задачи, и начните итерацию по всем элементам nums с указателем i = 0. Каждая итерация внешнего цикла представляет собой поиск одного диапазона. Для начала сохраните начало текущего диапазона в start = nums[i].

2⃣Проверьте, отличается ли следующий элемент в nums на индексе i + 1 от nums[i] на 1 или больше. Если следующий элемент отличается на 1, увеличьте i на 1, чтобы включить (i+1)-й элемент в этот диапазон и продолжайте проверку следующего элемента. Продолжайте добавлять элементы в этот диапазон, пока последовательные элементы отличаются на 1, используя цикл while для выполнения этой логики.

3⃣Если следующий элемент отличается более чем на 1 или если все элементы в nums уже обработаны, проверьте, равен ли start значению nums[i]. Если start == nums[i], добавьте только start как строку в ranges, так как у нас есть только один элемент в этом диапазоне. Если start != nums[i], добавьте строку start->nums[i] в ranges. Увеличьте i на 1, чтобы начать новый диапазон.

😎 Решение:
class Solution {
summaryRanges(nums) {
let ranges = []
let i = 0
while (i < nums.length) {
let start = nums[i]
while (i + 1 < nums.length && nums[i] + 1 == nums[i + 1]) {
i++
}
if (start != nums[i]) {
ranges.push(`${start}->${nums[i]}`)
} else {
ranges.push(`${start}`)
}
i++
}
return ranges
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 443. String Compression
Сложность: medium

Дан массив символов chars, сжать его, используя следующий алгоритм:
Начните с пустой строки s. Для каждой группы последовательных повторяющихся символов в chars:
Если длина группы равна 1, добавьте символ к строке s.
В противном случае добавьте символ, а за ним длину группы.
Сжатая строка s не должна возвращаться отдельно, а вместо этого должна быть сохранена в входном массиве символов chars. Обратите внимание, что длины групп, которые равны 10 или более, будут разделены на несколько символов в chars.
После того как вы закончите модификацию входного массива, верните новую длину массива.
Вы должны написать алгоритм, который использует только постоянное дополнительное пространство.

Пример:
Input: chars = ["a","a","b","b","c","c","c"]
Output: Return 6, and the first 6 characters of the input array should be: ["a","2","b","2","c","3"]
Explanation: The groups are "aa", "bb", and "ccc". This compresses to "a2b2c3".


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

1⃣Объявите переменные i – первый индекс текущей группы, и res – длина ответа (сжатой строки). Инициализируйте i = 0, res = 0.

2⃣Пока i меньше длины chars: Найдите длину текущей группы последовательных повторяющихся символов groupLength. Добавьте chars[i] к ответу (chars[res++] = chars[i]). Если groupLength > 1, добавьте строковое представление groupLength к ответу и увеличьте res соответственно. Увеличьте i на groupLength и перейдите к следующей группе.

3⃣Верните res.

😎 Решение:
var compress = function(chars) {
let i = 0, res = 0;
while (i < chars.length) {
let groupLength = 1;
while (i + groupLength < chars.length && chars[i + groupLength] === chars[i]) {
groupLength++;
}
chars[res++] = chars[i];
if (groupLength > 1) {
let strRepr = groupLength.toString();
for (let ch of strRepr) {
chars[res++] = ch;
}
}
i += groupLength;
}
return res;
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 923. 3Sum With Multiplicity
Сложность: medium

Если задан целочисленный массив arr и целое число target, верните количество кортежей i, j, k, таких, что i < j < k и arr[i] + arr[j] + arr[k] == target. Поскольку ответ может быть очень большим, верните его по модулю 10^9 + 7.

Пример:
Input: arr = [1,1,2,2,3,3,4,4,5,5], target = 8
Output: 20


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

1⃣Отсортировать массив arr.

2⃣Инициализировать счетчик для количества кортежей.
Пройти по массиву тремя указателями i, j, и k:
Для каждого i, установить j на i + 1, и k на конец массива.
Использовать двухуказательный метод для нахождения пар (j, k), таких что arr[i] + arr[j] + arr[k] == target.

3⃣Вернуть результат по модулю 10^9 + 7.

😎 Решение:
var threeSumMulti = function(arr, target) {
arr.sort((a, b) => a - b);
const MOD = 10**9 + 7;
let count = 0;

for (let i = 0; i < arr.length; i++) {
let j = i + 1, k = arr.length - 1;

while (j < k) {
let sum = arr[i] + arr[j] + arr[k];
if (sum === target) {
if (arr[j] === arr[k]) {
count += (k - j + 1) * (k - j) / 2;
break;
} else {
let left = 1, right = 1;
while (j + 1 < k && arr[j] === arr[j + 1]) {
left++;
j++;
}
while (k - 1 > j && arr[k] === arr[k - 1]) {
right++;
k--;
}
count += left * right;
j++;
k--;
}
} else if (sum < target) {
j++;
} else {
k--;
}
}
}

return count % MOD;
};


Ставь 👍 и забирай 📚 Базу знаний
Задача: 93. Restore IP Addresses
Сложность: medium

Сообщение, содержащее буквы от A до Z, можно закодировать в числа с использованием следующего соответствия:

- 'A' -> "1"
- 'B' -> "2"
- ...
- 'Z' -> "26"

Для декодирования закодированного сообщения все цифры должны быть сгруппированы и затем отображены обратно в буквы с использованием обратного соответствия (существует несколько способов). Например, "11106" можно представить как:

- "AAJF" с группировкой (1 1 10 6)
- "KJF" с группировкой (11 10 6)

Обратите внимание, что группировка (1 11 06) недопустима, потому что "06" не может быть преобразовано в 'F', так как "6" отличается от "06".

Для данной строки s, содержащей только цифры, верните количество способов декодирования.

Тестовые случаи сформированы таким образом, что ответ укладывается в 32-битное целое число.

Пример:
Input: s = "12"
Output: 2
Explanation: "12" could be decoded as "AB" (1 2) or "L" (12).


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

1️⃣Входим в рекурсию с данной строкой, начиная с индекса 0.

2️⃣Для окончательного случая рекурсии мы проверяем конец строки. Если мы достигли конца строки, возвращаем 1. Каждый раз, когда мы входим в рекурсию, это для подстроки исходной строки. Если первый символ в подстроке равен 0, то прекращаем этот путь, возвращая 0. Таким образом, этот путь не будет влиять на количество способов.

3️⃣Мемоизация помогает снизить сложность, которая иначе была бы экспоненциальной. Мы проверяем словарь memo, чтобы увидеть, существует ли уже результат для данной подстроки. Если результат уже находится в memo, мы возвращаем этот результат. В противном случае количество способов для данной строки определяется путем рекурсивного вызова функции с индексом +1 для следующей подстроки и индексом +2 после проверки на валидность двузначного декодирования. Результат также сохраняется в memo с ключом как текущий индекс, чтобы сохранить его для будущих пересекающихся подзадач.


😎 Решение:
var restoreIpAddresses = function (s) {
var ans = [];
function valid(s, start, length) {
return (
length == 1 ||
(s.charAt(start) != "0" &&
(length < 3 || s.substring(start, start + length) <= 255))
);
}
function helper(s, startIndex, dots, ans) {
var remainingLength = s.length - startIndex;
var remainingNumberOfIntegers = 4 - dots.length;
if (
remainingLength > remainingNumberOfIntegers * 3 ||
remainingLength < remainingNumberOfIntegers
) {
return;
}
if (dots.length == 3) {
if (valid(s, startIndex, remainingLength)) {
var temp = "";
var last = 0;
for (var dot of dots) {
temp += s.substring(last, last + dot) + ".";
last += dot;
}
temp += s.substring(startIndex);
ans.push(temp);
}
return;
}
for (
var curPos = 1;
curPos <= 3 && curPos <= remainingLength;
++curPos
) {
dots.push(curPos);
if (valid(s, startIndex, curPos)) {
helper(s, startIndex + curPos, dots, ans);
}
dots.pop();
}
}
helper(s, 0, [], ans);
return ans;
};


Ставь 👍 и забирай 📚 Базу знаний
Завтра последний день!

Успей купить пожизненный easyoffer PRO - по цене 1 года

Покупаешь один раз — пользуешься всю жизнь.

👉 Акция до 31 марта: https://easyoffer.ru/pro
Задача: 903. Valid Permutations for DI Sequence
Сложность: hard

Вам дана строка s длины n, где s[i] либо: 'D' означает убывание, либо 'I' означает возрастание. Перестановка perm из n + 1 целых чисел всех целых чисел в диапазоне [0, n] называется допустимой, если для всех допустимых i: если s[i] == 'D', то perm[i] > perm[i + 1], а если s[i] == 'I', то perm[i] < perm[i + 1]. Верните количество допустимых перестановок perm. Поскольку ответ может быть большим, верните его по модулю 109 + 7.

Пример:
Input: s = "DID"
Output: 5


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

1⃣Создать двумерный массив dp, где dp[i][j] представляет количество допустимых перестановок длины i, оканчивающихся на j.

2⃣Заполнить массив dp, учитывая условия возрастания и убывания из строки s.

3⃣Вернуть сумму dp[n][j] для всех j, что даст количество допустимых перестановок длины n + 1.

😎 Решение:
var numPermsDISequence = function(s) {
const MOD = 1e9 + 7;
const n = s.length;
const dp = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0));
dp[0][0] = 1;

for (let i = 1; i <= n; i++) {
for (let j = 0; j <= i; j++) {
if (s[i - 1] === 'D') {
dp[i][j] = dp[i - 1].slice(j, i).reduce((sum, x) => (sum + x) % MOD, 0);
} else {
dp[i][j] = dp[i - 1].slice(0, j).reduce((sum, x) => (sum + x) % MOD, 0);
}
}
}

return dp[n].reduce((sum, x) => (sum + x) % MOD, 0);
};


Ставь 👍 и забирай 📚 Базу знаний
👍1