Java | LeetCode
6.69K subscribers
218 photos
1.34K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.me/+icUwivvbGOkwNWRi
Вопросы собесов t.me/+7ESm0VKXC4tjYzky
Вакансии t.me/+4pspF5nDjgM4MjQy
Download Telegram
Задача: 216. Combination Sum III
Сложность: medium

Найдите все допустимые комбинации k чисел, которые в сумме дают n, при условии, что:

Используются только числа от 1 до 9.
Каждое число используется не более одного раза.
Верните список всех возможных допустимых комбинаций. Список не должен содержать одинаковые комбинации, и комбинации могут возвращаться в любом порядке.

Пример:
Input: k = 3, n = 9
Output: [[1,2,6],[1,3,5],[2,3,4]]
Explanation:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
There are no other valid combinations.


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

1⃣Инициализация и запуск рекурсивной функции:
Создайте вспомогательную функцию backtrack, которая принимает текущую оставшуюся сумму, размер комбинации k, текущую комбинацию, индекс следующего элемента для добавления и список результатов.
Инициализируйте пустые векторы для хранения текущей комбинации и всех возможных результатов.
Запустите функцию backtrack с начальными значениями: полной суммой n, размером комбинации k, пустой комбинацией, начальным индексом 0 и пустым списком результатов.

2⃣Рекурсивная обработка:
В функции backtrack проверьте, если текущая сумма равна нулю и размер комбинации равен k, добавьте копию текущей комбинации в список результатов.
Если текущая сумма меньше нуля или размер комбинации равен k, прекратите текущую ветвь обработки.
Иначе, итерируйтесь по оставшимся кандидатам, начиная с текущего индекса. Для каждого кандидата добавьте его в текущую комбинацию, обновите оставшуюся сумму и вызовите backtrack с обновленными параметрами. После возвращения из рекурсивного вызова удалите последний элемент из комбинации для рассмотрения следующего кандидата.

3⃣Возвращение результатов:
По завершении всех рекурсивных вызовов функция combinationSum3 возвращает список всех найденных комбинаций.

😎 Решение:
class Solution {
protected void backtrack(
int remain,
int k,
LinkedList<Integer> comb,
int next_start,
List<List<Integer>> results
) {
if (remain == 0 && comb.size() == k) {
results.add(new ArrayList<Integer>(comb));
return;
} else if (remain < 0 || comb.size() == k) {
return;
}

for (int i = next_start; i < 9; ++i) {
comb.add(i + 1);
this.backtrack(remain - i - 1, k, comb, i + 1, results);
comb.removeLast();
}
}

public List<List<Integer>> combinationSum3(int k, int n) {
List<List<Integer>> results = new ArrayList<List<Integer>>();
LinkedList<Integer> comb = new LinkedList<Integer>();

this.backtrack(n, k, comb, 0, results);
return results;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1160. Find Words That Can Be Formed by Characters
Сложность: easy

Вам дан массив строк words и строка chars.

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

Верните сумму длин всех хороших строк в words.

Пример:
Input: words = ["cat","bt","hat","tree"], chars = "atach"
Output: 6
Explanation: The strings that can be formed are "cat" and "hat" so the answer is 3 + 3 = 6.


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

1⃣Создайте хеш-таблицу counts, которая будет записывать частоту каждого символа в chars. Инициализируйте переменную ans = 0.

2⃣Итерируйте по каждому слову в words. Создайте хеш-таблицу wordCount, которая будет записывать частоту каждого символа в слове. Установите good = true. Итерируйте по каждому ключу c в wordCount. Пусть freq = wordCount[c]. Если counts[c] < freq, установите good = false и прервите цикл.

3⃣Если good = true, добавьте длину слова к ans. Верните ans.

😎 Решение:
class Solution {
public int countCharacters(String[] words, String chars) {
Map<Character, Integer> counts = new HashMap<>();
for (char c : chars.toCharArray()) {
counts.put(c, counts.getOrDefault(c, 0) + 1);
}

int ans = 0;
for (String word : words) {
Map<Character, Integer> wordCount = new HashMap<>();
for (char c : word.toCharArray()) {
wordCount.put(c, wordCount.getOrDefault(c, 0) + 1);
}

boolean good = true;
for (Map.Entry<Character, Integer> entry : wordCount.entrySet()) {
if (counts.getOrDefault(entry.getKey(), 0) < entry.getValue()) {
good = false;
break;
}
}

if (good) {
ans += word.length();
}
}

return ans;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1257. Smallest Common Region
Сложность: medium

Вам даны списки регионов, в которых первый регион каждого списка включает все остальные регионы этого списка. Естественно, если регион x содержит другой регион y, то x больше y. Также, по определению, регион x содержит сам себя. Даны два региона: region1 и region2, верните наименьший регион, который содержит их оба. Если вам даны регионы r1, r2 и r3 такие, что r1 включает r3, то гарантированно не существует r2 такого, что r2 включает r3. Гарантированно существует наименьший регион.

Пример:
Input:
regions = [["Earth","North America","South America"],
["North America","United States","Canada"],
["United States","New York","Boston"],
["Canada","Ontario","Quebec"],
["South America","Brazil"]],
region1 = "Quebec",
region2 = "New York"
Output: "North America"


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

1⃣Построим дерево регионов, где каждый регион указывает на своего родителя.

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

3⃣Найдем последний общий регион в путях двух заданных регионов.

😎 Решение:
import java.util.*;

public class Solution {
public String findSmallestRegion(List<List<String>> regions, String region1, String region2) {
Map<String, String> parent = new HashMap<>();

for (List<String> regionList : regions) {
for (int i = 1; i < regionList.size(); i++) {
parent.put(regionList.get(i), regionList.get(0));
}
}

Set<String> ancestors1 = new HashSet<>();
while (region1 != null) {
ancestors1.add(region1);
region1 = parent.get(region1);
}

while (!ancestors1.contains(region2)) {
region2 = parent.get(region2);
}

return region2;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 418. Sentence Screen Fitting
Сложность: medium

Если задан экран rows x cols и предложение, представленное в виде списка строк, верните количество раз, которое данное предложение может быть помещено на экран. Порядок слов в предложении должен оставаться неизменным, и слово не может быть разбито на две строки. Два последовательных слова в строке должны разделяться одним пробелом.

Пример:
Input: sentence = ["hello","world"], rows = 2, cols = 8
Output: 1


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

1⃣Преобразуйте предложение в единую строку с пробелами между словами и пробелом в конце.

2⃣Инициализируйте переменную для отслеживания текущей позиции в строке предложения. Для каждой строки экрана добавляйте количество символов, равное числу столбцов.

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

😎 Решение:
public class Solution {
public int wordsTyping(String[] sentence, int rows, int cols) {
String sentenceStr = String.join(" ", sentence) + " ";
int length = sentenceStr.length();
int count = 0;

for (int i = 0; i < rows; i++) {
count += cols;
if (sentenceStr.charAt(count % length) == ' ') {
count++;
} else {
while (count > 0 && sentenceStr.charAt((count - 1) % length) != ' ') {
count--;
}
}
}

return count / length;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 460. LFU Cache
Сложность: hard

Спроектируйте и реализуйте структуру данных для кеша с наименьшим количеством использования (Least Frequently Used, LFU).

Реализуйте класс LFUCache:

LFUCache(int capacity): Инициализирует объект с указанной вместимостью структуры данных.
int get(int key): Возвращает значение ключа, если ключ существует в кеше. В противном случае возвращает -1.
void put(int key, int value): Обновляет значение ключа, если он уже присутствует, или вставляет ключ, если его еще нет. Когда кеш достигает своей вместимости, он должен аннулировать и удалить ключ, используемый наименее часто, перед вставкой нового элемента. В этой задаче, если имеется несколько ключей с одинаковой частотой использования, аннулируется наименее недавно использованный ключ.
Чтобы определить наименее часто используемый ключ, для каждого ключа в кеше поддерживается счетчик использования. Ключ с наименьшим счетчиком использования является наименее часто используемым ключом.
Когда ключ впервые вставляется в кеш, его счетчик использования устанавливается на 1 (из-за операции put). Счетчик использования для ключа в кеше увеличивается при вызове операции get или put для этого ключа.

Функции get и put должны иметь среднюю временную сложность O(1).

Пример:
Input
["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
Output
[null, null, null, 1, null, -1, 3, null, -1, 3, 4]


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

1⃣insert(int key, int frequency, int value):
Вставить пару частота-значение в cache с заданным ключом.
Получить LinkedHashSet, соответствующий данной частоте (по умолчанию пустой Set), и вставить в него ключ.

2⃣int get(int key):
Если ключа нет в кеше, вернуть -1.
Получить частоту и значение из кеша.
Удалить ключ из LinkedHashSet, связанного с частотой.
Если minf == frequency и LinkedHashSet пуст, увеличить minf на 1 и удалить запись частоты из frequencies.
Вызвать insert(key, frequency + 1, value).
Вернуть значение.

3⃣void put(int key, int value):
Если capacity <= 0, выйти.
Если ключ существует, обновить значение и вызвать get(key).
Если размер кеша равен capacity, удалить первый элемент из LinkedHashSet, связанного с minf, и из кеша.
Установить minf в 1.
Вызвать insert(key, 1, value).

😎 Решение:
import java.util.*;

class LFUCache {
private Map<Integer, LinkedHashSet<int[]>> frequencies;
private Map<Integer, int[]> cache;
private int capacity;
private int minf;

public LFUCache(int capacity) {
this.capacity = capacity;
this.minf = 0;
this.cache = new HashMap<>();
this.frequencies = new HashMap<>();
}

private void insert(int key, int frequency, int value) {
frequencies.computeIfAbsent(frequency, k -> new LinkedHashSet<>()).add(new int[]{key, value});
cache.put(key, new int[]{frequency, value});
}

public int get(int key) {
if (!cache.containsKey(key)) return -1;
int[] entry = cache.get(key);
int frequency = entry[0];
int value = entry[1];
frequencies.get(frequency).remove(entry);
if (frequencies.get(frequency).isEmpty()) {
frequencies.remove(frequency);
if (minf == frequency) minf++;
}
insert(key, frequency + 1, value);
return value;
}

public void put(int key, int value) {
if (capacity <= 0) return;
if (cache.containsKey(key)) {
cache.get(key)[1] = value;
get(key);
return;
}
if (cache.size() == capacity) {
int[] leastUsed = frequencies.get(minf).iterator().next();
frequencies.get(minf).remove(leastUsed);
if (frequencies.get(minf).isEmpty()) frequencies.remove(minf);
cache.remove(leastUsed[0]);
}
minf = 1;
insert(key, 1, value);
}
}


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

Дано целочисленный массив nums. Соседние целые числа в nums будут выполнять деление с плавающей запятой.

Например, для nums = [2,3,4] мы будем вычислять выражение "2/3/4".
Однако, вы можете добавить любое количество скобок в любое место, чтобы изменить приоритет операций. Вы хотите добавить эти скобки так, чтобы значение выражения после вычисления было максимальным.

Верните соответствующее выражение, которое имеет максимальное значение в строковом формате.

Пример:
Input: nums = [1000,100,10,2]
Output: "1000/(100/10/2)"
Explanation: 1000/(100/10/2) = 1000/((100/10)/2) = 200
However, the bold parenthesis in "1000/((100/10)/2)" are redundant since they do not influence the operation priority.
So you should return "1000/(100/10/2)".
Other cases:
1000/(100/10)/2 = 50
1000/(100/(10/2)) = 50
1000/100/10/2 = 0.5
1000/100/(10/2) = 2


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

1⃣Разверните оба числа. Инициализируйте массив ans с (N+M) нулями. Для каждой цифры во втором числе: держите переменную переноса, изначально равную 0. Инициализируйте массив (currentResult), начинающийся с некоторых нулей в зависимости от места цифры во втором числе.

2⃣Для каждой цифры первого числа: умножьте цифру второго числа на цифру первого числа и добавьте предыдущий перенос к результату умножения. Возьмите остаток от деления на 10, чтобы получить последнюю цифру. Добавьте последнюю цифру к массиву currentResult. Разделите результат умножения на 10, чтобы получить новое значение переноса.

3⃣После итерации по каждой цифре в первом числе, если перенос не равен нулю, добавьте перенос к массиву currentResult. Добавьте currentResult к массиву ans. Если последняя цифра в ans равна нулю, перед разворотом ans удалите этот ноль, чтобы избежать ведущего нуля в окончательном ответе. Разверните ans и верните его.

😎 Решение:
class Solution {
private List<Integer> addStrings(List<Integer> num1, List<Integer> num2) {
List<Integer> ans = new ArrayList<>();
int carry = 0;
int n1 = num1.size();
int n2 = num2.size();

for (int i = 0; i < Math.max(n1, n2) + 1; ++i) {
int digit1 = i < n1 ? num1.get(i) : 0;
int digit2 = i < n2 ? num2.get(i) : 0;
int sum = digit1 + digit2 + carry;
carry = sum / 10;
ans.add(sum % 10);
}
return ans;
}

private List<Integer> multiplyOneDigit(String firstNumber, char secondNumberDigit, int numZeros) {
List<Integer> currentResult = new ArrayList<>(Collections.nCopies(numZeros, 0));
int carry = 0;

for (char digit : firstNumber.toCharArray()) {
int multiplication = (secondNumberDigit - '0') * (digit - '0') + carry;
carry = multiplication / 10;
currentResult.add(multiplication % 10);
}
if (carry != 0) {
currentResult.add(carry);
}
return currentResult;
}

public String multiply(String firstNumber, String secondNumber) {
if (firstNumber.equals("0") || secondNumber.equals("0")) {
return "0";
}

firstNumber = new StringBuilder(firstNumber).reverse().toString();
secondNumber = new StringBuilder(secondNumber).reverse().toString();

List<Integer> ans = new ArrayList<>(Collections.nCopies(firstNumber.length() + secondNumber.length(), 0));

for (int i = 0; i < secondNumber.length(); ++i) {
ans = addStrings(multiplyOneDigit(firstNumber, secondNumber.charAt(i), i), ans);
}

while (ans.get(ans.size() - 1) == 0) {
ans.remove(ans.size() - 1);
}

StringBuilder answer = new StringBuilder();
for (int i = ans.size() - 1; i >= 0; --i) {
answer.append(ans.get(i));
}

return answer.toString();
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: №27. Remove Element
Сложность: easy

Дан массив nums и целое число val.
Удалите все вхождения val на месте.
Порядок элементов можно изменить.
Верните количество элементов k, не равных val, таких что nums[0..k-1] содержат нужные значения.

Пример:
Input: nums = [3,2,2,3], val = 3 Output: 2 (nums = [2,2,_,_])


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

1⃣Завести указатель left_most_index = 0, чтобы отслеживать позицию следующего "нужного" элемента.

2⃣Пройти по всем элементам массива:
- Если текущий элемент не равен val, поместить его на позицию left_most_index и сдвинуть указатель.

3⃣По завершению вернуть left_most_index, т.е. количество элементов, не равных val.

😎 Решение:
class Solution {
public int removeElement(int[] nums, int val) {
int left_most_index = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != val) {
nums[left_most_index++] = nums[i];
}
}
return left_most_index;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 925. Long Pressed Name
Сложность: easy

Ваш друг набирает на клавиатуре свое имя. Иногда, при наборе символа c, клавиша может быть долго нажата, и символ будет набран 1 или более раз. Вы исследуете набранные символы клавиатуры. Верните True, если возможно, что это было имя вашего друга, при этом некоторые символы (возможно, ни один) не были долго нажаты.

Пример:
Input: name = "alex", typed = "aaleex"
Output: true


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

1⃣Инициализировать два указателя i и j для строки имени и набранной строки соответственно.

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

3⃣Вернуть True, если все символы имени были обработаны, иначе False.

😎 Решение:
class Solution {
public boolean isLongPressedName(String name, String typed) {
int i = 0, j = 0;
while (j < typed.length()) {
if (i < name.length() && name.charAt(i) == typed.charAt(j)) {
i++;
} else if (j == 0 || typed.charAt(j) != typed.charAt(j - 1)) {
return false;
}
j++;
}
return i == name.length();
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 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 {
public boolean isPowerOfTwo(int n) {
if (n == 0) return false;
long x = n;
return (x & -x) == x;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1237. Find Positive Integer Solution for a Given Equation
Сложность: medium

Если дана вызываемая функция f(x, y) со скрытой формулой и значением z, выполните обратную разработку формулы и верните все пары целых положительных чисел x и y, в которых f(x,y) == z. Пары можно возвращать в любом порядке. Хотя точная формула скрыта, функция является монотонно возрастающей, т.е.Например: f(x, y) < f(x + 1, y) f(x, y) < f(x, y + 1) Интерфейс функции определяется следующим образом: interface CustomFunction { public: // Возвращает некоторое положительное целое число f(x, y) для двух положительных целых чисел x и y на основе формулы.
int f(int x, int y); }; Мы будем оценивать ваше решение следующим образом: у судьи есть список из 9 скрытых реализаций CustomFunction, а также способ сгенерировать ключ ответа из всех допустимых пар для определенного z. Судья получит два входа: function_id (чтобы определить, с какой реализацией тестировать ваш код) и целевое z. Судья вызовет ваш findSolution и сравнит ваши результаты с ключом ответа. Если ваши результаты совпадут с ключом ответа, ваше решение будет принято.

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


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

1⃣Начнем с =1 x=1 и 𝑦=1000 y=1000 (предполагаем максимальное значение y).

2⃣Перемещение указателей:
Если 𝑓(𝑥,𝑦)=𝑧
f(x,y)=z, добавляем пару (𝑥,𝑦)(x,y) в результат и увеличиваем x.

3⃣Повторяем шаги до тех пор, пока
𝑥≤1000 x≤1000 и 𝑦≥1y≥1.

😎 Решение:
import java.util.ArrayList;
import java.util.List;

public class CustomFunction {
public int f(int x, int y) {}
}

public class Solution {
public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {
List<List<Integer>> result = new ArrayList<>();
int x = 1;
int y = 1000;

while (x <= 1000 && y >= 1) {
int value = customfunction.f(x, y);
if (value == z) {
List<Integer> pair = new ArrayList<>();
pair.add(x);
pair.add(y);
result.add(pair);
x++;
} else if (value < z) {
x++;
} else {
y--;
}
}

return result;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1278. Palindrome Partitioning III
Сложность: hard

Вам дана строка s, содержащая строчные буквы, и целое число k. Вам нужно: Сначала заменить некоторые символы s на другие строчные английские буквы. Затем разделить s на k непустых непересекающихся подстрок так, чтобы каждая подстрока была палиндромом. Верните минимальное количество символов, которое нужно изменить, чтобы разделить строку.

Пример:
Input: s = "abc", k = 2
Output: 1


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

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

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

3⃣Верните минимальное количество изменений, найденное во втором шаге.

😎 Решение:
public class Solution {
public int minChangesToMakePalindrome(String s, int k) {
int n = s.length();

int[][] dp1 = new int[n][n];
for (int length = 1; length <= n; length++) {
for (int i = 0; i <= n - length; i++) {
int j = i + length - 1;
dp1[i][j] = minChangeToPalindrome(s, i, j);
}
}

int[][] dp2 = new int[n + 1][k + 1];
for (int[] row : dp2) {
Arrays.fill(row, Integer.MAX_VALUE);
}
dp2[0][0] = 0;

for (int i = 1; i <= n; i++) {
for (int kk = 1; kk <= k; kk++) {
for (int j = 0; j < i; j++) {
dp2[i][kk] = Math.min(dp2[i][kk], dp2[j][kk - 1] + dp1[j][i - 1]);
}
}
}

return dp2[n][k];
}

private int minChangeToPalindrome(String s, int i, int j) {
int changes = 0;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
changes++;
}
i++;
j--;
}
return changes;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 762. Prime Number of Set Bits in Binary Representation
Сложность: hard

Если даны два целых числа left и right, верните количество чисел в диапазоне [left, right], имеющих простое число битов в двоичном представлении. Напомним, что число битов в двоичном представлении - это количество единиц, присутствующих в числе 1. Например, 21 в двоичном представлении - это 10101, которое имеет 3 бита.

Пример:
Input: left = 10, right = 15
Output: 5


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

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

2⃣Создайте функцию для проверки, является ли число простым.

3⃣Пройдите через все числа в диапазоне [left, right] и подсчитайте числа, у которых количество битов в двоичном представлении является простым числом.

😎 Решение:
public class Solution {
public int countPrimeSetBits(int left, int right) {
int count = 0;
for (int num = left; num <= right; num++) {
if (isPrime(Integer.bitCount(num))) {
count++;
}
}
return count;
}

private boolean isPrime(int x) {
if (x < 2) return false;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0) return false;
}
return true;
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1012. Numbers With Repeated Digits
Сложность: hard

Задав целое число n, верните количество положительных целых чисел в диапазоне [1, n], у которых хотя бы одна цифра повторяется.

Пример:
Input: n = 20
Output: 1


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

1⃣Вычисление всех чисел с уникальными цифрами:
Найдите количество чисел в диапазоне [1, n], у которых все цифры уникальны. Для этого используйте перебор всех чисел и проверку уникальности цифр.

2⃣Вычисление всех чисел в диапазоне [1, n]:
Это значение равно n, поскольку это количество всех чисел от 1 до n включительно.

3⃣Вычисление результата:
Вычтите количество чисел с уникальными цифрами из общего количества чисел, чтобы получить количество чисел с повторяющимися цифрами.

😎 Решение:
public class Solution {
public int numDupDigitsAtMostN(int n) {
return n - countUniqueDigitNumbers(n);
}

private int countUniqueDigitNumbers(int x) {
String s = Integer.toString(x);
int n = s.length();
int res = 0;

for (int i = 1; i < n; i++) {
res += 9 * permutation(9, i - 1);
}

Set<Integer> used = new HashSet<>();
for (int i = 0; i < n; i++) {
for (int j = (i == 0 ? 1 : 0); j < s.charAt(i) - '0'; j++) {
if (!used.contains(j)) {
res += permutation(9 - i, n - i - 1);
}
}
if (used.contains(s.charAt(i) - '0')) {
break;
}
used.add(s.charAt(i) - '0');
}

if (used.size() == n) {
res++;
}

return res;
}

private int permutation(int m, int n) {
return n == 0 ? 1 : permutation(m, n - 1) * (m - n + 1);
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1125. Smallest Sufficient Team
Сложность: hard

В проекте у вас есть список необходимых навыков req_skills и список людей. i-й человек people[i] содержит список навыков, которыми обладает этот человек.

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

Например, команда = [0, 1, 3] представляет людей с навыками people[0], people[1] и people[3].
Верните любую достаточную команду наименьшего возможного размера, представленную индексами каждого человека. Вы можете вернуть ответ в любом порядке.

Гарантируется, что ответ существует.

Пример:
Input: req_skills = ["algorithms","math","java","reactjs","csharp","aws"], people = [["algorithms","math","java"],["algorithms","math","reactjs"],["java","csharp","aws"],["reactjs","csharp"],["csharp","math"],["aws","java"]]
Output: [1,2]


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

1⃣Инициализация и создание масок навыков:
Определите количество людей n и количество необходимых навыков m.
Создайте хэш-таблицу skillId, чтобы сопоставить каждому навыку уникальный индекс.
Создайте массив skillsMaskOfPerson, который будет содержать битовые маски навыков для каждого человека.

2⃣Динамическое программирование (DP):
Создайте массив dp размера 2^m и заполните его значениями (1 << n) - 1.
Установите dp[0] в 0 (базовый случай).
Для каждого skillsMask от 1 до 2^m - 1:
- для каждого человека i:
- вычислите smallerSkillsMask как skillsMask & ~skillsMaskOfPerson[i].
- если smallerSkillsMask отличается от skillsMask, обновите dp[skillsMask], если новая команда лучше (имеет меньше установленных битов).

3⃣Формирование ответа:
Извлеките ответ из dp и верните массив индексов людей, составляющих минимальную достаточную команду.

😎 Решение:
class Solution {
public int[] smallestSufficientTeam(String[] req_skills, List<List<String>> people) {
int n = people.size(), m = req_skills.length;
HashMap<String, Integer> skillId = new HashMap<String, Integer>();
for (int i = 0; i < m; i++) {
skillId.put(req_skills[i], i);
}
int skillsMaskOfPerson[] = new int[n];
for (int i = 0; i < n; i++) {
for (String skill : people.get(i)) {
skillsMaskOfPerson[i] |= 1 << skillId.get(skill);
}
}
long dp[] = new long [1 << m];
Arrays.fill(dp, (1L << n) - 1);
dp[0] = 0;
for (int skillsMask = 1; skillsMask < (1 << m); skillsMask++) {
for (int i = 0; i < n; i++) {
int smallerSkillsMask = skillsMask & ~skillsMaskOfPerson[i];
if (smallerSkillsMask != skillsMask) {
long peopleMask = dp[smallerSkillsMask] | (1L << i);
if (Long.bitCount(peopleMask) < Long.bitCount(dp[skillsMask])) {
dp[skillsMask] = peopleMask;
}
}
}
}
long answerMask = dp[(1 << m) - 1];
int ans[] = new int [Long.bitCount(answerMask)];
int ptr = 0;
for (int i = 0; i < n; i++) {
if (((answerMask >> i) & 1) == 1) {
ans[ptr++] = i;
}
}
return ans;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1396. Design Underground System
Сложность: medium

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

Реализуйте класс UndergroundSystem:
- void checkIn(int id, string stationName, int t)
Пассажир с карточкой, идентификатор которой равен id, регистрируется на станции stationName в момент времени t.
Пассажир может быть зарегистрирован только в одном месте в одно и то же время.
- void checkOut(int id, string stationName, int t)
Пассажир с карточкой, идентификатор которой равен id, покидает станцию stationName в момент времени t.
- double getAverageTime(string startStation, string endStation)
Возвращает среднее время, необходимое для поездки от startStation до endStation.
Среднее время вычисляется на основе всех предыдущих временных интервалов поездок от startStation до endStation, которые происходили непосредственно, т.е. регистрация на startStation с последующим выходом на endStation.
Время, необходимое для поездки от startStation до endStation, может отличаться от времени поездки от endStation до startStation.
Перед вызовом getAverageTime будет как минимум один пассажир, который уже совершил поездку от startStation до endStation.
Вы можете предположить, что все вызовы методов checkIn и checkOut являются последовательными. Если пассажир регистрируется в момент времени t1, а затем выходит в момент времени t2, то t1 < t2. Все события происходят в хронологическом порядке.

Пример:
Input
["UndergroundSystem","checkIn","checkOut","getAverageTime","checkIn","checkOut","getAverageTime","checkIn","checkOut","getAverageTime"]
[[],[10,"Leyton",3],[10,"Paradise",8],["Leyton","Paradise"],[5,"Leyton",10],[5,"Paradise",16],["Leyton","Paradise"],[2,"Leyton",21],[2,"Paradise",30],["Leyton","Paradise"]]
Output
[null,null,null,5.00000,null,null,5.50000,null,null,6.66667]
Explanation
UndergroundSystem undergroundSystem = new UndergroundSystem();
undergroundSystem.checkIn(10, "Leyton", 3);
undergroundSystem.checkOut(10, "Paradise", 8); // Customer 10 "Leyton" -> "Paradise" in 8-3 = 5
undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.00000, (5) / 1 = 5
undergroundSystem.checkIn(5, "Leyton", 10);
undergroundSystem.checkOut(5, "Paradise", 16); // Customer 5 "Leyton" -> "Paradise" in 16-10 = 6
undergroundSystem.getAverageTime("Leyton", "Paradise"); // return 5.50000, (5 + 6) / 2 = 5.5
undergroundSystem.checkIn(2, "Leyton", 21);


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

1⃣При регистрации на входе сохраняем информацию о начале пути (станция и время) в словаре checkInData.

2⃣При регистрации на выходе извлекаем информацию о начале пути из checkInData, вычисляем время поездки и обновляем статистику для маршрута в journeyData.

3⃣Для получения среднего времени поездки по заданному маршруту извлекаем статистику из journeyData и вычисляем среднее значение.

😎 Решение:
class UndergroundSystem {
private Map<String, double[]> journeyData = new HashMap<>();
private Map<Integer, Pair<String, Integer>> checkInData = new HashMap<>();

public void checkIn(int id, String stationName, int t) {
checkInData.put(id, new Pair<>(stationName, t));
}

public void checkOut(int id, String stationName, int t) {
Pair<String, Integer> checkIn = checkInData.remove(id);
String routeKey = checkIn.getKey() + "->" + stationName;
double tripTime = t - checkIn.getValue();
journeyData.computeIfAbsent(routeKey, k -> new double[2]);
journeyData.get(routeKey)[0] += tripTime;
journeyData.get(routeKey)[1] += 1;
}

public double getAverageTime(String startStation, String endStation) {
double[] stats = journeyData.get(startStation + "->" + endStation);
return stats[0] / stats[1];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 665. Non-decreasing Array
Сложность: medium

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

Мы определяем массив как неубывающий, если для каждого i (индексация с 0), такого что 0 <= i <= n - 2, выполняется условие nums[i] <= nums[i + 1].

Пример:
Input: nums = [4,2,3]
Output: true
Explanation: You could modify the first 4 to 1 to get a non-decreasing array.


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

1⃣Инициализация переменных:
Завести переменную count для подсчета числа изменений.
Проверить последовательность чисел в массиве nums.

2⃣Проверка условий:
Если nums[i] > nums[i + 1], то увеличиваем count.
Если count превышает 1, возвращаем false, так как больше одного изменения недопустимо.
Если nums[i - 1] > nums[i + 1] и nums[i] > nums[i + 2], то возвращаем false.

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

😎 Решение:
public class Solution {
public boolean checkPossibility(int[] nums) {
int count = 0;

for (int i = 1; i < nums.length; i++) {
if (nums[i] < nums[i - 1]) {
if (count > 0) {
return false;
}
count++;
if (i == 1 || nums[i] >= nums[i - 2]) {
nums[i - 1] = nums[i];
} else {
nums[i] = nums[i - 1];
}
}
}

return true;
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1269. Number of Ways to Stay in the Same Place After Some Steps
Сложность: hard

У вас есть указатель на индекс 0 в массиве размера arrLen. На каждом шаге вы можете перемещаться на 1 позицию влево, на 1 позицию вправо в массиве или оставаться на том же месте (указатель ни в коем случае не должен находиться за пределами массива). Учитывая два целых числа steps и arrLen, верните количество способов, при которых указатель все еще находится на индексе 0 после ровно шагов. Поскольку ответ может быть слишком большим, верните его по модулю 10^9 + 7.

Пример:
Input: steps = 3, arrLen = 2
Output: 4


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

1⃣Инициализируйте массив для хранения количества способов достижения каждого индекса на каждом шаге.

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

3⃣Используйте динамическое программирование для подсчета количества способов достижения каждого индекса на каждом шаге.

😎 Решение:
public class Solution {
public int numWays(int steps, int arrLen) {
int mod = 1000000007;
int max_pos = Math.min(arrLen - 1, steps);
int[] dp = new int[max_pos + 1];
dp[0] = 1;
for (int step = 0; step < steps; step++) {
int[] new_dp = new int[max_pos + 1];
for (int i = 0; i <= max_pos; i++) {
new_dp[i] = dp[i] % mod;
if (i > 0) new_dp[i] = (new_dp[i] + dp[i - 1]) % mod;
if (i < max_pos) new_dp[i] = (new_dp[i] + dp[i + 1]) % mod;
}
dp = new_dp;
}
return dp[0];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1143. Longest Common Subsequence
Сложность: medium

Даны две строки text1 и text2. Верните длину их наибольшей общей подпоследовательности. Если общей подпоследовательности нет, верните 0.

Подпоследовательность строки — это новая строка, созданная из оригинальной строки путем удаления некоторых символов (может быть ни одного) без изменения относительного порядка оставшихся символов.
Например, "ace" является подпоследовательностью "abcde".
Общая подпоследовательность двух строк — это подпоследовательность, которая является общей для обеих строк.

Пример:
Input: text1 = "abcde", text2 = "ace" 
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.


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

1⃣Создайте двумерный массив memo для хранения промежуточных результатов, чтобы избежать повторных вычислений. Инициализируйте массив значением -1, чтобы указать, что эти ячейки еще не были рассчитаны.

2⃣Реализуйте рекурсивную функцию memoSolve, которая принимает два указателя на текущие позиции в text1 и text2 и возвращает длину наибольшей общей подпоследовательности для этих подстрок. Если текущие символы совпадают, добавьте 1 к результату рекурсивного вызова для следующих символов. Если не совпадают, найдите максимум между рекурсивными вызовами с измененными указателями.

3⃣Возвращайте значение memoSolve(0, 0), чтобы получить результат для всей строки.

😎 Решение:
class Solution {

private int[][] memo;
private String text1;
private String text2;

public int longestCommonSubsequence(String text1, String text2) {
this.memo = new int[text1.length() + 1][text2.length() + 1];
for (int i = 0; i < text1.length(); i++) {
for (int j = 0; j < text2.length(); j++) {
this.memo[i][j] = -1;
}
}
this.text1 = text1;
this.text2 = text2;
return memoSolve(0, 0);
}

private int memoSolve(int p1, int p2) {
if (memo[p1][p2] != -1) {
return memo[p1][p2];
}

int answer = 0;
if (text1.charAt(p1) == text2.charAt(p2)) {
answer = 1 + memoSolve(p1 + 1, p2 + 1);
} else {
answer = Math.max(memoSolve(p1, p2 + 1), memoSolve(p1 + 1, p2));
}

memo[p1][p2] = answer;
return memo[p1][p2];
}
}


Ставь 👍 и забирай 📚 Базу знаний
Задача: 1379. Find a Corresponding Node of a Binary Tree in a Clone of That Tree
Сложность: easy

Даны два бинарных дерева: original и cloned, а также ссылка на узел target в оригинальном дереве.

Дерево cloned является копией оригинального дерева.

Верните ссылку на тот же узел в дереве cloned.

Обратите внимание, что вам не разрешается изменять какое-либо из двух деревьев или узел target, и ответ должен быть ссылкой на узел в дереве cloned.

Пример:
Input: tree = [7,4,3,null,null,6,19], target = 3
Output: 3
Explanation: In all examples the original and cloned trees are shown. The target node is a green node from the original tree. The answer is the yellow node from the cloned tree.


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

1⃣Добавьте корень в очередь.

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

3⃣Верните ссылку на найденный целевой узел.

😎 Решение:
class Solution {
public final TreeNode getTargetCopy(final TreeNode original, final TreeNode cloned, final TreeNode target) {
Deque<TreeNode> queue_o = new ArrayDeque<>();
queue_o.offer(original);

Deque<TreeNode> queue_c = new ArrayDeque<>();
queue_c.offer(cloned);

while (!queue_o.isEmpty()) {
TreeNode node_o = queue_o.poll();
TreeNode node_c = queue_c.poll();

if (node_o == target) {
return node_c;
}

if (node_o.left != null) {
queue_o.offer(node_o.left);
queue_c.offer(node_c.left);
}
if (node_o.right != null) {
queue_o.offer(node_o.right);
queue_c.offer(node_c.right);
}
}
return null;
}
}


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

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

Верните число с наибольшим значением, которое можно получить.

Пример:
Input: num = 2736
Output: 7236
Explanation: Swap the number 2 and the number 7.


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

1⃣Сохраняем кандидатов как списки длины len(num). Для каждой пары позиций (i, j) выполняем обмен цифр, записываем кандидата, если он больше текущего ответа, затем возвращаем цифры обратно.

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

3⃣Возвращаем максимальное значение из всех кандидатов.

😎 Решение:
class Solution {
public int maximumSwap(int num) {
char[] A = Integer.toString(num).toCharArray();
char[] ans = Arrays.copyOf(A, A.length);
for (int i = 0; i < A.length; i++) {
for (int j = i + 1; j < A.length; j++) {
char tmp = A[i];
A[i] = A[j];
A[j] = tmp;
for (int k = 0; k < A.length; k++) {
if (A[k] != ans[k]) {
if (A[k] > ans[k]) {
ans = Arrays.copyOf(A, A.length);
}
break;
}
}
A[j] = A[i];
A[i] = tmp;
}
}
return Integer.valueOf(new String(ans));
}
}


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