Golang | LeetCode
3.94K subscribers
173 photos
1.05K links
Cайт easyoffer.ru
Реклама @easyoffer_adv
ВП @easyoffer_vp

Тесты t.iss.one/+MVqzqT6ZzFFhYjhi
Вопросы собесов t.iss.one/+ajHN0OKU1okyZDky
Вакансии t.iss.one/+mX_RBWjiMTExODUy
Download Telegram
Задача: 22. Generate Parentheses #medium
Условие:
Учитывая n пар круглых скобок, напишите функцию, которая генерирует все комбинации правильных круглых скобок.

Решение:
func generateParenthesis(n int) []string {
res := make([]string, 0)
backtrack(&res,n,0,0,"")
return res
}

func backtrack(res *[]string, n, openN, closedN int, val string) {
if openN == closedN && openN == n {
*res = append(*res, val)
return
}
if openN < n {
backtrack(res, n, openN + 1, closedN, val + "(")
}
if closedN < openN {
backtrack(res, n, openN, closedN + 1, val + ")")
}
}

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

Функция generateparenthesis создает пустой список res, вызывает функцию backtrack и возвращает результат.

backtrack функция принимает указатель на список res, число N, текущее количество открывающих и закрывающих скобок, и текущую строку val.

Если количество открывающих и закрывающих скобок равно N, то мы добавляем текущую строку val в список res и возвращаемся.

Если количество открывающих скобок меньше N, мы вызываем backtrack с увеличенным количеством открывающих скобок.

Если количество закрывающих скобок меньше открывающих, мы вызываем backtrack с увеличенным количеством закрывающих скобок.

Таким образом, рекурсивно перебираются все возможные комбинации скобок, пока не будет достигнуто необходимое количество открывающих и закрывающих скобок.
1👍1🤔1
Задача: 23. Merge k Sorted Lists #hard
Условие:
Вам дан массив из k списков связанных списков, каждый связанный список отсортирован в порядке возрастания.

Объедините все связанные списки в один отсортированный связанный список и верните его.

Решение:
import "container/heap"

type PQueue []*ListNode

func (pq PQueue) Len() int {
return len(pq)
}

func (pq PQueue) Less(i, j int) bool {
return pq[i].Val < pq[j].Val
}

func (pq PQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}

func (pq *PQueue) Push(x interface{}) {
item := x.(*ListNode)
*pq = append(*pq, item)
}

func (pq *PQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
old[n-1] = nil
*pq = old[0 : n-1]
return item
}

func (pq *PQueue) Peek(x interface{}) *ListNode {
return (*pq)[len(*pq)-1]
}

func mergeKLists(lists []*ListNode) *ListNode {
top := &ListNode{
Val: -1,
}
merged := top
pq := PQueue{}
for _, l := range lists {
if l != nil {
pq = append(pq, l)
}
}
heap.Init(&pq)
for len(pq) > 0 {
merged.Next = heap.Pop(&pq).(*ListNode)
merged = merged.Next
if merged.Next != nil {
heap.Push(&pq, merged.Next)
}
}
return top.Next
}

Пояснение:
Этот код реализует алгоритм слияния k отсортированных связанных списков.

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

Методы len, less, swap определены для работы с очередью. Методы push и pop добавляют и удаляют элементы из очереди соответственно. Метод peek возвращает элемент с наивысшим приоритетом (с наименьшим значением) в очереди.

Функция mergeklists принимает массив lists содержащий указатели на начало каждого отсортированного связанного списка. Алгоритм начинает слияние, добавляя все не пустые списки в приоритетную очередь. Затем в цикле извлекается узел с наименьшим значением из очереди и добавляется к результату слияния. Если узел имеет следующий элемент, он добавляется обратно в очередь. Этот процесс продолжается, пока очередь не станет пустой.

В конце функция возвращает указатель на первый элемент нового отсортированного связанного списка.

Использование стратегии слияния списков с помощью приоритетной очереди позволяет эффективно объединять множество уже отсортированных списков в один отсортированный список.
👍1🤔1
Задача: 23. Merge k Sorted Lists #hard
Условие:
Вам дан массив из k списков связанных списков, каждый связанный список отсортирован в порядке возрастания.

Объедините все связанные списки в один отсортированный связанный список и верните его.

Решение:
import "container/heap"

type PQueue []*ListNode

func (pq PQueue) Len() int {
return len(pq)
}

func (pq PQueue) Less(i, j int) bool {
return pq[i].Val < pq[j].Val
}

func (pq PQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}

func (pq *PQueue) Push(x interface{}) {
item := x.(*ListNode)
*pq = append(*pq, item)
}

func (pq *PQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
old[n-1] = nil
*pq = old[0 : n-1]
return item
}

func (pq *PQueue) Peek(x interface{}) *ListNode {
return (*pq)[len(*pq)-1]
}

func mergeKLists(lists []*ListNode) *ListNode {
top := &ListNode{
Val: -1,
}
merged := top
pq := PQueue{}
for _, l := range lists {
if l != nil {
pq = append(pq, l)
}
}
heap.Init(&pq)
for len(pq) > 0 {
merged.Next = heap.Pop(&pq).(*ListNode)
merged = merged.Next
if merged.Next != nil {
heap.Push(&pq, merged.Next)
}
}
return top.Next
}

Пояснение:
Этот код реализует алгоритм слияния k отсортированных связанных списков.

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

Методы len, less, swap определены для работы с очередью. Методы push и pop добавляют и удаляют элементы из очереди соответственно. Метод peek возвращает элемент с наивысшим приоритетом (с наименьшим значением) в очереди.

Функция mergeklists принимает массив lists содержащий указатели на начало каждого отсортированного связанного списка. Алгоритм начинает слияние, добавляя все не пустые списки в приоритетную очередь. Затем в цикле извлекается узел с наименьшим значением из очереди и добавляется к результату слияния. Если узел имеет следующий элемент, он добавляется обратно в очередь. Этот процесс продолжается, пока очередь не станет пустой.

В конце функция возвращает указатель на первый элемент нового отсортированного связанного списка.

Использование стратегии слияния списков с помощью приоритетной очереди позволяет эффективно объединять множество уже отсортированных списков в один отсортированный список.
👍1🔥1🤔1
Задача: 23. Merge k Sorted Lists #hard
Условие:
Вам дан массив из k списков связанных списков, каждый связанный список отсортирован в порядке возрастания.

Объедините все связанные списки в один отсортированный связанный список и верните его.

Решение:
import "container/heap"

type PQueue []*ListNode

func (pq PQueue) Len() int {
return len(pq)
}

func (pq PQueue) Less(i, j int) bool {
return pq[i].Val < pq[j].Val
}

func (pq PQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
}

func (pq *PQueue) Push(x interface{}) {
item := x.(*ListNode)
*pq = append(*pq, item)
}

func (pq *PQueue) Pop() interface{} {
old := *pq
n := len(old)
item := old[n-1]
old[n-1] = nil
*pq = old[0 : n-1]
return item
}

func (pq *PQueue) Peek(x interface{}) *ListNode {
return (*pq)[len(*pq)-1]
}

func mergeKLists(lists []*ListNode) *ListNode {
top := &ListNode{
Val: -1,
}
merged := top
pq := PQueue{}
for _, l := range lists {
if l != nil {
pq = append(pq, l)
}
}
heap.Init(&pq)
for len(pq) > 0 {
merged.Next = heap.Pop(&pq).(*ListNode)
merged = merged.Next
if merged.Next != nil {
heap.Push(&pq, merged.Next)
}
}
return top.Next
}

Пояснение:
Этот код реализует алгоритм слияния k отсортированных связанных списков.

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

Методы len, less, swap определены для работы с очередью. Методы push и pop добавляют и удаляют элементы из очереди соответственно. Метод peek возвращает элемент с наивысшим приоритетом (с наименьшим значением) в очереди.

Функция mergeklists принимает массив lists содержащий указатели на начало каждого отсортированного связанного списка. Алгоритм начинает слияние, добавляя все не пустые списки в приоритетную очередь. Затем в цикле извлекается узел с наименьшим значением из очереди и добавляется к результату слияния. Если узел имеет следующий элемент, он добавляется обратно в очередь. Этот процесс продолжается, пока очередь не станет пустой.

В конце функция возвращает указатель на первый элемент нового отсортированного связанного списка.

Использование стратегии слияния списков с помощью приоритетной очереди позволяет эффективно объединять множество уже отсортированных списков в один отсортированный список.
👍21
Задача: 24. Swap Nodes in Pairs
#medium
Условие:
Учитывая связанный список, поменяйте местами каждые два соседних узла и верните его голову. Вы должны решить проблему, не изменяя значения в узлах списка (т. е. изменять можно только сами узлы).

Решение:
func swapPairs(head *ListNode) *ListNode {
newHead := &ListNode{
Next: head,
}
prev := newHead
i := head
if i == nil {
return head
}
j := head.Next
for i != nil && j != nil {
prev.Next = j
i.Next = j.Next
j.Next = i
if i.Next == nil {
break
}
prev, i, j = i, i.Next, i.Next.Next
}
return newHead.Next
}

Пояснение:
Конечно! Давайте разберем этот код подробнее:


Этот код представляет собой функцию `swapPairs`, которая меняет местами каждую пару соседних элементов в связанном списке.

1. Создается новый узел `newHead`, который инициализируется как исходный головной узел списка.

2. Устанавливается указатель `prev` на `newHead`, который будет использоваться для обновления связей между узлами списка.

3. Устанавливаются указатели `i` и `j` на начальный и следующий после него узлы соответственно.

4. Происходит проверка, если начальный узел `i` равен `nil` (конец списка), то возвращается исходный головной узел.

5. В цикле происходит обмен значениями между узлами:
- `prev.Next` указывает на `j` (следующий после `i`)
- `i.Next` указывает на `j.Next` (следующий после `j`)
- `j.Next` указывает на `i`
- Если `i.Next` равен `nil`, то прерывается цикл
- Обновляются указатели `prev`, `i`, `j` на следующие узлы
6. После завершения цикла возвращается головной узел списка, указываемый `newHead.Next`, который стал новым первым элементом после обмена.


Таким образом, функция `swapPairs` меняет местами каждую пару соседних узлов в связанном списке, используя указатели для изменения связей между узлами.
👍21🤔1
Задача: 25. Reverse Nodes in k-Group #hard
Условие:
Учитывая заголовок связанного списка, поменяйте местами узлы списка k за раз и верните измененный список.

k — целое положительное число, меньшее или равное длине связанного списка. Если количество узлов не кратно k, то пропущенные узлы в конечном итоге должны остаться такими, какие они есть.

Вы не можете изменять значения в узлах списка, можно изменять только сами узлы

Решение:
func reverseKGroup(head *ListNode, k int) *ListNode {
top := &ListNode{
Next: head,
}
track := top
for track.Next != nil {
last, after := reverse(track.Next, k)
if last == nil {
return top.Next
}
track.Next.Next = after
track.Next, track = last, track.Next
}
return top.Next
}

func reverse(n *ListNode, remaining int) (last, after *ListNode) {
if remaining == 1 {
return n, n.Next
}
if n.Next == nil {
return nil, nil
}
last, after = reverse(n.Next, remaining-1)
if last == nil {
return nil, nil
}
n.Next.Next = n
return
}

Пояснение:
Функция `reverseKGroup`:
1. Создается новый узел `top`, который инициализируется как исходный головной узел списка.
2. Устанавливается указатель `track` на `top`, который используется для отслеживания текущего положения в списке.
3. В цикле выполняется следующие действия для каждой группы из k элементов:
- Вызывается функция `reverse`, которая возвращает последний узел (`last`) и узел, следующий за группой (`after`).
- Если возвращенный `last` равен `nil`, значит достигнут конец списка и происходит возврат нового головного узла `top.Next`.
- В противном случае, устанавливаются связи между узлами в группе.
- Обновляется указатель `track` на последний узел текущей группы, и продолжается обход списка.
4. По завершении обхода списка возвращается новый головной узел `top.Next`.

Функция `reverse`:
1. Функция рекурсивно меняет порядок следования узлов в группе размером `k`.
2. Если остался один элемент в группе (remaining == 1), то возвращается последний узел и узел, следующий за ним.
3. Если узел `n` является последним в списке, то возвращаются `nil` значения.
4. Иначе, функция вызывается рекурсивно с узлом следующим за `n` и уменьшением оставшегося количества элементов.
5. После рекурсивного вызова меняется порядок следования узлов в группе.
6. Возвращаются узлы `last` (последний элемент в группе) и `after` (узел, следующий за группой).

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

https://leetcode.com/problems/reverse-nodes-in-k-group
👍1🤔1
Задача: 26. Remove Duplicates from Sorted Array #easy
Условие:
Учитывая целочисленный массив чисел, отсортированный в неубывающем порядке, удалите дубликаты на месте так, чтобы каждый уникальный элемент появлялся только один раз. Относительный порядок элементов должен оставаться неизменным. Затем верните количество уникальных элементов в числах.

Считайте, что количество уникальных элементов чисел равно k. Чтобы вас приняли, вам нужно сделать следующее:

Измените массив nums так, чтобы первые k элементов nums содержали уникальные элементы в том порядке, в котором они присутствовали в nums изначально. Остальные элементы nums не важны, как и размер nums.
Вернуть К.

Решение:
func removeDuplicates(nums []int) int {
if len(nums) <= 1 {
return len(nums)
}
i, j := 0, 1
for j < len(nums) {
if nums[i] == nums[j] {
j++
} else {
nums[i+1] = nums[j]
i++
j++
}
}
return i+1
}

Пояснение:
Функция `removeDuplicates` принимает на вход срез целых чисел `nums` и выполняет удаление дубликатов из него. Она возвращает количество уникальных элементов и обновляет исходный срез, удаляя дубликаты.

1. Сначала проверяется длина среза `nums`. Если он меньше или равен 1, то функция мгновенно возвращает длину среза (т.е. в таком случае нет дубликатов).
2. Инициализируются два указателя `i` и `j`, где `i` указывает на начало уникальной части массива, а `j` на следующий за `i` элемент.
3. Запускается цикл, который проходит по элементам массива, начиная с элемента под индексом `1`.
4. На каждой итерации проверяется, равны ли элементы под индексами `i` и `j`.
- Если элементы равны, инкрементируется `j`, чтобы пропустить дубликат.
- В противном случае элемент под индексом `j` копируется на позицию `i+1`, увеличивается `i` и `j` на 1.
5. Процесс продолжается до тех пор, пока `j` не достигнет конца массива.
6. Функция возвращает `i+1`, что соответствует количеству уникальных элементов в массиве.

Таким образом, данная функция эффективно удаляет дубликаты из среза, меняя элементы на месте и возвращая количество уникальных элементов.
👍2🤔1
Задача: 27. Remove Element #easy
Условие:
Учитывая целочисленный массив nums и целочисленное значение, удалите все вхождения val в nums на месте. Порядок элементов может быть изменен. Затем верните количество элементов в виде чисел, которые не равны val.

Учитывайте количество элементов в nums, которые не равны val be k. Чтобы вас приняли, вам необходимо сделать следующее:

Измените массив nums так, чтобы первые k элементов nums содержали элементы, не равные val. Остальные элементы nums не важны, как и размер nums.
Вернуть К.

Решение:
func removeElement(a []int, val int) int {
n := len(a)
j := 0
for i := 0; i<n; i++ {
if a[i] != val {
a[j] = a[i]
j++
}
}
return j
}

Пояснение:
Функция `removeElement` принимает на вход срез целых чисел `a` и значение `val`, которое нужно удалить из среза. Функция возвращает количество элементов в срезе после удаления указанного значения.

1. Длина среза `a` сохраняется в переменной `n`.
2. Инициализируется переменная `j`, которая будет использоваться для обновления индексов в срезе.
3. В цикле `for` проходятся все элементы среза от `0` до `n-1` индекса.
4. На каждой итерации проверяется, равен ли элемент `a[i]` значению `val`.
- Если элемент не равен заданному значению `val`, то он копируется на позицию `a[j]` и значение переменной `j` увеличивается на `1`.
- Это позволяет обновить срез таким образом, чтобы значения, отличные от `val`, были скопированы в начало среза.
5. После завершения цикла возвращается значение `j`, которое является количеством элементов в срезе после удаления всех элементов со значением `val`.

Таким образом, данная функция эффективно удаляет все встречающиеся значения `val` из среза, сдвигая оставшиеся элементы в начало и возвращая количество оставшихся элементов.
🤔1
Задача: 28. Find the Index of the First Occurrence in a String #easy
Условие:
Учитывая две строки, игла и стог сена, верните индекс первого вхождения иглы в стоге сена или -1, если игла не является частью стога сена

Решение:
func strStr(haystack string, needle string) int {
n := len(needle)
if n==0 {
return 0
}
for i:=0; i<len(haystack)-n+1; i++ {
if haystack[i:i+n] == needle {
return i
}
}
return -1
}

Пояснение:
Функция `strStr` принимает две строки `haystack` и `needle` и ищет первое вхождение подстроки `needle` в строку `haystack`. Если подстрока не найдена, функция возвращает -1, иначе возвращает индекс, с которого начинается найденная подстрока.

1. Получаем длину строки `needle` и сохраняем ее в переменную `n`.
2. Проверяем, если длина `needle` равна нулю, то сразу возвращаем индекс 0, так как пустая подстрока считается встроенной в любую строку.
3. В цикле `for` перебираем индексы от 0 до `len(haystack)-n+1`, чтобы не выходить за пределы строки `haystack` в попытке сравнения подстроки.
4. На каждой итерации сравниваем подстроку `haystack[i:i+n]` с подстрокой `needle`.
- Если они равны, возвращаем индекс `i`, который указывает на начало вхождения подстроки.
5. Если цикл завершается и ни одно вхождение не было найдено, функция возвращает -1.

Таким образом, данная функция выполняет поиск первого вхождения подстроки `needle` в строку `haystack`, путем сравнения подстрок на каждой позиции.
🤔1
Задача: 29. Divide Two Integers #medium
Условие:
Учитывая два целых числа: делимое и делитель, разделите два целых числа, не используя операторы умножения, деления и модификатора.

Целочисленное деление должно сокращаться до нуля, что означает потерю дробной части. Например, 8,345 будет сокращено до 8, а -2,7335 будет сокращено до -2.

Возвращает частное после деления делимого на делитель.

Примечание. Предположим, мы имеем дело со средой, которая может хранить целые числа только в пределах 32-битного целого диапазона со знаком: [−2^31, 2^31 − 1]. В этой задаче, если частное строго больше 2^31 - 1, верните 2^31 - 1, а если частное строго меньше -2^31, верните -2^31.

Решение:
func divide(dividend int, divisor int) int {
c := 0
dividendS, r := SplitSignAndNum(dividend)
divisorS, d := SplitSignAndNum(divisor)
for r >= d {
r = r - d
c++
}

result := c

if dividendS < 0 || divisorS < 0 {
if !(dividendS < 0 && divisorS < 0) {
result = -c
}
}

if result < -2147483648 {
return -2147483648
} else if result > 2147483647 {
return 2147483647
}

return result
}

func SplitSignAndNum(i int) (int,int) {
if i < 0 {
return -1, -i
}
return 1, i
}

Пояснение:
Функция `divide` выполняет деление двух целых чисел `dividend` на `divisor`. Если результат деления выходит за пределы 32-битного целого числа, возвращает максимально возможное целое число (INT_MAX или INT_MIN).

1. Инициализируется переменная `c` для хранения результата деления.
2. `dividend` и `divisor` обрабатываются функцией `SplitSignAndNum`, которая возвращает знак (1 или -1) и абсолютное значение числа.
3. В цикле вычитаем из `r` (абсолютное значение делимого) `d` (абсолютное значение делителя) и увеличиваем счетчик `c`, пока `r >= d`.
4. Результат деления сохраняется в переменной `result`.
5. Если исходные числа были отрицательными, результат деления корректируется со знаком.
6. Проверяется переполнение результата по условиям INT_MIN и INT_MAX.
7. Возвращается результат.

Функция `SplitSignAndNum` просто возвращает знак числа и его абсолютное значение. Если число меньше 0, знак будет -1, иначе 1.

Этот код эмулирует деление целых чисел без использования встроенного оператора деления, обрабатывает знаки чисел и контролирует переполнение результата.
🤔1
Задача: 30. Substring with Concatenation of All Words #hard
Условие:
Вам дана строка s и массив строк-слов. Все строки слов имеют одинаковую длину.

Объединенная строка — это строка, которая в точности содержит все строки любой перестановки объединенных слов.

Например, если слова = ["ab", "cd", "ef"], то "abcdef", "abefcd", "cdabef", "cdefab", "efabcd" и "efcdab" являются объединенными строками. «acdbef» не является объединенной строкой, поскольку не является объединением какой-либо перестановки слов.
Возвращает массив начальных индексов всех объединенных подстрок в s. Вы можете вернуть ответ в любом порядке

Решение:
import "sort"

func findSubstring(s string, words []string) []int {
sort.Strings(words)
var result []int
for i := range s {
if startsWithConcatenatedSubstring(s[i:], words) {
result = append(result, i)
}
}
return result
}

func startsWithConcatenatedSubstring(s string, words []string) bool {
var parts[] string
wl := len(words[0])
for i := range words {
startPos := wl*i
endPos := wl*(i+1)
if endPos > len(s) {
return false
}
p := s[startPos:endPos]
parts = append(parts, p)
}
sort.Strings(parts)
for i, p := range parts {
if p != words[i] {
return false
}
}
return true
}

Пояснение:
Функция `findSubstring` выполняет поиск всех начальных индексов в строке `s`, с которых начинается подстрока, образованная конкатенацией всех слов из среза `words`. Функция использует функцию `startsWithConcatenatedSubstring` для проверки соответствия подстроки условиям.

1. Сначала все слова в срезе `words` сортируются в лексикографическом порядке.
2. Создается пустой срез `result` для хранения индексов начала подстрок.
3. В цикле `for`, проходящем по индексам строки `s`, проверяется, начинается ли подстрока с индексом `i` с конкатенации слов из `words` (используется функция `startsWithConcatenatedSubstring`). Если условие выполняется, индекс `i` добавляется в срез `result`.
4. Возвращается срез `result` с индексами начала подстрок.

Функция `startsWithConcatenatedSubstring` проверяет, начинается ли переданная строка `s` с конкатенации слов из среза `words`:
1. Создается временный срез `parts`, куда добавляются подстроки длиной `wl` (длина одного слова) из строки `s`.
2. Подстроки также сортируются в лексикографическом порядке.
3. Проверяется соответствие каждой подстроки с элементом из среза `words`. Если хоть одна подстрока не соответствует, возвращается `false`.
4. Если все проверки успешны, возвращается `true`, указывая на то, что строка `s` начинается с конкатенации слов из `words`.

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

https://leetcode.com/problems/substring-with-concatenation-of-all-words
🤔1
#medium
Задача: 31. Next Permutation

Перестановка массива целых чисел — это упорядочивание его элементов в последовательность или линейный порядок.

Например, для arr = [1,2,3] следующие являются всеми перестановками arr: [1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1].
Следующая перестановка массива целых чисел — это следующая лексикографически большая перестановка его чисел. Более формально, если все перестановки массива отсортированы в одном контейнере по лексикографическому порядку, то следующая перестановка этого массива — это перестановка, следующая за ней в отсортированном контейнере. Если такое упорядочивание невозможно, массив должен быть переупорядочен в наименьший возможный порядок (то есть отсортирован по возрастанию).

Например, следующая перестановка arr = [1,2,3] — это [1,3,2].
Аналогично, следующая перестановка arr = [2,3,1] — это [3,1,2].
В то время как следующая перестановка arr = [3,2,1] — это [1,2,3], потому что [3,2,1] не имеет лексикографически большего переупорядочивания.

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

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

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


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

1️⃣Мы меняем местами числа a[i−1] и a[j]. Теперь у нас есть правильное число на индексе i−1. Однако текущая перестановка ещё не является той перестановкой, которую мы ищем. Нам нужна наименьшая перестановка, которая может быть сформирована с использованием чисел только справа от a[i−1]. Следовательно, нам нужно расположить эти числа в порядке возрастания, чтобы получить их наименьшую перестановку.

2️⃣Однако, вспомните, что, сканируя числа справа налево, мы просто уменьшали индекс, пока не нашли пару a[i] и a[i−1], где a[i] > a[i−1]. Таким образом, все числа справа от a[i−1] уже были отсортированы в порядке убывания. Более того, обмен местами a[i−1] и a[j] не изменил этот порядок.

3️⃣Поэтому нам просто нужно перевернуть числа, следующие за a[i−1], чтобы получить следующую наименьшую лексикографическую перестановку.

😎 Решение:
func nextPermutation(nums []int) {
i := len(nums) - 2
for i >= 0 && nums[i+1] <= nums[i] {
i--
}
if i >= 0 {
j := len(nums) - 1
for nums[j] <= nums[i] {
j--
}
swap(nums, i, j)
}
reverse(nums, i+1)
}

func reverse(nums []int, start int) {
i, j := start, len(nums)-1
for i < j {
swap(nums, i, j)
i++
j--
}
}

func swap(nums []int, i int, j int) {
temp := nums[i]
nums[i] = nums[j]
nums[j] = temp
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#hard
Задача: 32. Longest Valid Parentheses

Дана строка, содержащая только символы '(' и ')'. Верните длину самой длинной подстроки с корректными (правильно сформированными) скобками.

Пример:
Input: s = "(()"
Output: 2


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

1️⃣В этом подходе мы рассматриваем каждую возможную непустую подстроку чётной длины из заданной строки и проверяем, является ли она корректной строкой скобок. Для проверки корректности мы используем метод стека.

2️⃣Каждый раз, когда мы встречаем символ ‘(’, мы кладём его в стек. Для каждого встреченного символа ‘)’ мы извлекаем из стека символ ‘(’. Если символ ‘(’ недоступен в стеке для извлечения в любой момент или если в стеке остались элементы после обработки всей подстроки, подстрока скобок является некорректной.

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

😎 Решение:
func isValid(s string) bool {
stack := []rune{}
for _, char := range s {
if char == '(' {
stack = append(stack, char)
} else if len(stack) > 0 && stack[len(stack)-1] == '(' {
stack = stack[:len(stack)-1]
} else {
return false
}
}
return len(stack) == 0
}

func longestValidParentheses(s string) int {
maxlen := 0
for i := 0; i < len(s); i++ {
for j := i + 2; j <= len(s); j += 2 {
if isValid(s[i:j]) {
if maxlen < j-i {
maxlen = j - i
}
}
}
}
return maxlen
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#medium
Задача: 33. Search in Rotated Sorted Array

Есть массив целых чисел nums, отсортированный в порядке возрастания (с уникальными значениями).

Перед передачей в вашу функцию массив nums может быть повёрнут в неизвестном индексе поворота k (1 <= k < nums.length), так что результирующий массив будет иметь вид [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]] (с индексацией с нуля). Например, [0,1,2,4,5,6,7] может быть повёрнут в индексе поворота 3 и стать [4,5,6,7,0,1,2].

Для данного массива nums после возможного поворота и целого числа target, верните индекс target, если он есть в массиве, или -1, если его нет в массиве.

Вы должны написать алгоритм с временной сложностью O(log n).

Пример:
Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4


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

1️⃣Выполните двоичный поиск для определения индекса поворота, инициализируя границы области поиска значениями left = 0 и right = n - 1. Пока left < right:
Пусть mid = left + (right - left) // 2.
Если nums[mid] > nums[n - 1], это предполагает, что точка поворота находится справа от mid, следовательно, мы устанавливаем left = mid + 1. В противном случае, поворот может находиться на позиции mid или левее от mid, в этом случае мы должны установить right = mid.

2️⃣По завершении двоичного поиска мы имеем индекс поворота, обозначенный как pivot = left.
nums состоит из двух отсортированных подмассивов, nums[0 ~ left - 1] и nums[left ~ n - 1].

3️⃣Выполните двоичный поиск по подмассиву nums[0 ~ left - 1] для поиска target. Если target находится в этом подмассиве, верните его индекс.
В противном случае выполните двоичный поиск по подмассиву nums[left ~ n - 1] для поиска target. Если target находится в этом подмассиве, верните его индекс. В противном случае верните -1.

😎 Решение:
func search(nums []int, target int) int {
n := len(nums)
left, right := 0, n-1
for left <= right {
mid := (left + right) / 2
if nums[mid] > nums[n-1] {
left = mid + 1
} else {
right = mid - 1
}
}
binarySearch := func(left_boundary int, right_boundary int, target int) int {
left, right := left_boundary, right_boundary
for left <= right {
mid := (left + right) / 2
if nums[mid] == target {
return mid
} else if nums[mid] > target {
right = mid - 1
} else {
left = mid + 1
}
}
return -1
}
if answer := binarySearch(0, left-1, target); answer != -1 {
return answer
}
return binarySearch(left, n-1, target)
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#medium
Задача: 34. Find First and Last Position of Element in Sorted Array

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

Если целевое значение не найдено в массиве, верните [-1, -1].

Вы должны написать алгоритм со временной сложностью O(log n).

Пример:
Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]


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

1️⃣Определите функцию под названием findBound, которая принимает три аргумента: массив, целевое значение для поиска и булевое значение isFirst, указывающее, ищем ли мы первое или последнее вхождение цели.
Мы используем 2 переменные для отслеживания подмассива, который мы сканируем. Назовем их begin и end. Изначально begin устанавливается в 0, а end — в последний индекс массива.

2️⃣Мы итерируем, пока begin не станет больше, чем end.
На каждом шаге мы вычисляем средний элемент mid = (begin + end) / 2. Мы используем значение среднего элемента, чтобы решить, какую половину массива нам нужно искать.
Если nums[mid] == target:
Если isFirst true — это означает, что мы пытаемся найти первое вхождение элемента. Если mid == begin или nums[mid - 1] != target, тогда мы возвращаем mid как первое вхождение цели. В противном случае мы обновляем end = mid - 1.
Если isFirst false — это означает, что мы пытаемся найти последнее вхождение элемента. Если mid == end или nums[mid + 1] != target, тогда мы возвращаем mid как последнее вхождение цели. В противном случае мы обновляем begin = mid + 1.

3️⃣Если nums[mid] > target — мы обновляем end = mid - 1, так как мы должны отбросить правую сторону массива, поскольку средний элемент больше цели.
Если nums[mid] < target — мы обновляем begin = mid + 1, так как мы должны отбросить левую сторону массива, поскольку средний элемент меньше цели.
В конце нашей функции мы возвращаем значение -1, что указывает на то, что цель не найдена в массиве.
В основной функции searchRange мы сначала вызываем findBound с isFirst, установленным в true. Если это значение равно -1, мы можем просто вернуть [-1, -1]. В противном случае мы вызываем findBound с isFirst, установленным в false, чтобы получить последнее вхождение, а затем возвращаем результат.

😎 Решение:
func searchRange(nums []int, target int) []int {
firstOccurrence := findBound(nums, target, true)
if firstOccurrence == -1 {
return []int{-1, -1}
}
lastOccurrence := findBound(nums, target, false)
return []int{firstOccurrence, lastOccurrence}
}

func findBound(nums []int, target int, isFirst bool) int {
N := len(nums)
begin, end := 0, N-1
for begin <= end {
mid := (begin + end) / 2
if nums[mid] == target {
if isFirst {
if mid == begin || nums[mid-1] != target {
return mid
}
end = mid - 1
} else {
if mid == end || nums[mid+1] != target {
return mid
}
begin = mid + 1
}
} else if nums[mid] > target {
end = mid - 1
} else {
begin = mid + 1
}
}
return -1
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
#easy
Задача: 35. Search Insert Position

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

Вы должны написать алгоритм со временной сложностью O(log n).

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


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

1️⃣Инициализируйте указатели left и right: left = 0, right = n - 1.

2️⃣Пока left <= right:
Сравните средний элемент массива nums[pivot] с целевым значением target.
Если средний элемент является целевым, то есть target == nums[pivot]: верните pivot.
Если цель не найдена:
Если target < nums[pivot], продолжайте поиск в левом подмассиве. right = pivot - 1.
Иначе продолжайте поиск в правом подмассиве. left = pivot + 1.
3️⃣Верните left.

😎 Решение:
func searchInsert(nums []int, target int) int {
var pivot, left, right int = 0, 0, len(nums) - 1
for left <= right {
pivot = left + (right-left)/2
if nums[pivot] == target {
return pivot
} else if target < nums[pivot] {
right = pivot - 1
} else {
left = pivot + 1
}
}
return left
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🤔1
#medium
Задача: 36. Valid Sudoku

Определите, является ли доска Судоку размером 9 на 9 валидной. Необходимо проверить только заполненные ячейки согласно следующим правилам:

Каждая строка должна содержать цифры от 1 до 9 без повторений.
Каждый столбец должен содержать цифры от 1 до 9 без повторений.
Каждая из девяти подзон размером 3 на 3 в сетке должна содержать цифры от 1 до 9 без повторений.

Пример:
Input: board = 
[["5","3",".",".","7",".",".",".","."]
,["6",".",".","1","9","5",".",".","."]
,[".","9","8",".",".",".",".","6","."]
,["8",".",".",".","6",".",".",".","3"]
,["4",".",".","8",".","3",".",".","1"]
,["7",".",".",".","2",".",".",".","6"]
,[".","6",".",".",".",".","2","8","."]
,[".",".",".","4","1","9",".",".","5"]
,[".",".",".",".","8",".",".","7","9"]]
Output: true


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

1️⃣Инициализируйте список из 9 хеш-множеств, где хеш-множество с индексом r будет использоваться для хранения ранее увиденных чисел в строке r судоку. Аналогично инициализируйте списки из 9 хеш-множеств для отслеживания столбцов и блоков.

2️⃣Итерируйтесь по каждой позиции (r, c) в судоку. На каждой итерации, если на текущей позиции есть число:
Проверьте, существует ли это число в хеш-множестве для текущей строки, столбца или блока. Если да, верните false, потому что это второе появление числа в текущей строке, столбце или блоке.

3️⃣В противном случае обновите множество, отвечающее за отслеживание ранее увиденных чисел в текущей строке, столбце и блоке. Индекс текущего блока рассчитывается как (r / 3) * 3 + (c / 3), где / означает деление нацело.
Если дубликаты не были найдены после посещения каждой позиции на доске судоку, то судоку валидно, поэтому верните true.

😎 Решение:
func isValidSudoku(board [][]byte) bool {
N := 9
rows := make([]map[byte]bool, N)
cols := make([]map[byte]bool, N)
boxes := make([]map[byte]bool, N)
for i := 0; i < N; i++ {
rows[i] = make(map[byte]bool)
cols[i] = make(map[byte]bool)
boxes[i] = make(map[byte]bool)
}
for r := 0; r < N; r++ {
for c := 0; c < N; c++ {
val := board[r][c]
if val == '.' {
continue
}
if rows[r][val] {
return false
}
rows[r][val] = true
if cols[c][val] {
return false
}
cols[c][val] = true
idx := (r/3)*3 + c/3
if boxes[idx][val] {
return false
}
boxes[idx][val] = true
}
}
return true
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1🤔1
#hard
Задача: 37. Sudoku Solver

Напишите программу для решения головоломки Судоку, заполнив пустые ячейки.

Решение Судоку должно удовлетворять всем следующим правилам:

Каждая из цифр от 1 до 9 должна встречаться ровно один раз в каждой строке.
Каждая из цифр от 1 до 9 должна встречаться ровно один раз в каждом столбце.
Каждая из цифр от 1 до 9 должна встречаться ровно один раз в каждом из 9 подблоков 3x3 сетки.
Символ '.' обозначает пустые ячейки.

Пример:
Input: board = 
[["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]]
Output:
[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]


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

1️⃣Теперь все готово для написания функции обратного поиска backtrack(row = 0, col = 0). Начните с верхней левой ячейки row = 0, col = 0. Продолжайте, пока не дойдете до первой свободной ячейки.

2️⃣Итерируйте по числам от 1 до 9 и попробуйте поставить каждое число d в ячейку (row, col).
Если число d еще не в текущей строке, столбце и блоке:
Поместите d в ячейку (row, col).
Запишите, что d теперь присутствует в текущей строке, столбце и блоке.

3️⃣Если вы на последней ячейке row == 8, col == 8:
Это означает, что судоку решено.
В противном случае продолжайте размещать дальнейшие числа.
Откат, если решение еще не найдено: удалите последнее число из ячейки (row, col).

😎 Решение:
type Solution struct {
n, N int
rows, columns, boxes [][]int
board [][]byte
}

func Constructor() Solution {
n, N := 3, 9
rows, columns, boxes := make([][]int, N), make([][]int, N), make([][]int, N)
for i := 0; i < N; i++ {
rows[i], columns[i], boxes[i] = make([]int, N+1), make([]int, N+1), make([]int, N+1)
}
return Solution{n: n, N: N, rows: rows, columns: columns, boxes: boxes}
}

func (s *Solution) couldPlace(d, row, col int) bool {
idx := (row / s.n) * s.n + col / s.n
return s.rows[row][d]+s.columns[col][d]+s.boxes[idx][d] == 0
}

func (s *Solution) placeOrRemove(d, row, col int, place bool) {
idx := (row / s.n) * s.n + col / s.n
delta := 1
if !place {
delta = -1
s.board[row][col] = '.'
} else {
s.board[row][col] = byte(d) + '0'
}
s.rows[row][d] += delta
s.columns[col][d] += delta
s.boxes[idx][d] += delta
}

func (s *Solution) backTrack(row, col int) bool {
if col == s.N {
col, row = 0, row+1
}
if row == s.N {
return true
}
if s.board[row][col] != '.' {
return s.backTrack(row, col+1)
}
for d := 1; d <= 9; d++ {
if s.couldPlace(d, row, col) {
s.placeOrRemove(d, row, col, true)
if s.backTrack(row, col+1) {
return true
}
s.placeOrRemove(d, row, col, false)
}
}
return false
}

func solveSudoku(board [][]byte) {
s := Constructor()
s.board = board
for r := 0; r < s.N; r++ {
for c := 0; c < s.N; c++ {
if board[r][c] != '.' {
d := int(board[r][c] - '0')
s.placeOrRemove(d, r, c, true)
}
}
}
s.backTrack(0, 0)
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
Пояснение:
Этот код написан для решения задачи Судоку путем заполнения доски с помощью рекурсивного алгоритма обратного отслеживания (backtracking).

1. В начале создаются 2D массивы `rows`, `cols`, `subs` для отслеживания, какие числа уже присутствуют в строках, столбцах и блоках.

2. Происходит проход по всем элементам доски `board`. Если ячейка не пустая, то соответствующие индексы в массивах `rows`, `cols`, `subs` устанавливаются в `true` для этого числа.

3. Далее определяется внутренняя рекурсивная функция `backtracking`, которая принимает координаты `x` и `y` текущей ячейки.

4. Если `x` (индекс строки) больше 8 (соответствует 9 строке), это означает, что все строки были заполнены, и решение Судоку найдено, возвращается `true`.

5. Затем вычисляются координаты следующей ячейки с учетом порядка заполнения доски (слева направо, сверху вниз).

6. Есть случай, когда текущая ячейка пустая. Тогда производится перебор чисел от 1 до 9:

- Для каждого числа проверяется, является ли оно подходящим для заполнения (не нарушается ли правило игры).
- Если число подходит, оно заполняется, отмечается в массивах `rows`, `cols`, `subs`, и запускается рекурсивный вызов для следующей ячейки.

- Если рекурсивный вызов вернул `true`, значит решение найдено и возвращается `true`.

- Если не удалось найти подходящее число, то текущая ячейка становится пустой, и процесс продолжается с новым числом.

7. Если текущая ячейка уже заполнена, просто выполняется рекурсивный вызов для следующей ячейки.

8. После прохождения всех ячеек доски и выполнения рекурсивных вызовов, если решение не было найдено, возвращается `false`. Если же решение найдено, доска будет заполнена правильно.

Этот код эффективно решает задачу Судоку, используя метод обратного отслеживания для проверки подходящих чисел и постепенного заполнения доски.
#medium
Задача: 38. Count and Say

Последовательность "считай и скажи" — это последовательность строк цифр, определяемая с помощью рекурсивной формулы:

countAndSay(1) = "1"
countAndSay(n) — это кодирование длин серий из countAndSay(n - 1).
Кодирование длин серий (RLE) — это метод сжатия строк, который работает путём замены последовательных идентичных символов (повторяющихся 2 или более раз) на конкатенацию символа и числа, обозначающего количество символов (длину серии). Например, чтобы сжать строку "3322251", мы заменяем "33" на "23", "222" на "32", "5" на "15" и "1" на "11". Таким образом, сжатая строка становится "23321511".

Для заданного положительного целого числа n верните n-й элемент последовательности "считай и скажи".

Пример:
Input: n = 4

Output: "1211"

Explanation:

countAndSay(1) = "1"
countAndSay(2) = RLE of "1" = "11"
countAndSay(3) = RLE of "11" = "21"
countAndSay(4) = RLE of "21" = "1211"


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

1️⃣Мы хотим использовать шаблон, который соответствует строкам из одинаковых символов, таких как "4", "7777", "2222222".
Если у вас есть опыт работы с регулярными выражениями, вы можете обнаружить, что шаблон (.)\1* работает.

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

3️⃣*: этот квалификатор, следующий за ссылкой на группу \1, указывает, что мы хотели бы видеть повторение группы ноль или более раз.
Таким образом, шаблон соответствует строкам, которые состоят из некоторого символа, а затем ноль или более повторений этого символа после его первого появления. Это то, что нам нужно.
Мы находим все совпадения с регулярным выражением, а затем конкатенируем результаты.

😎 Решение:
func countAndSay(n int) string {
s := "1"
for i := 2; i <= n; i++ {
t := ""
count := 1
for j := 1; j < len(s); j++ {
if s[j] == s[j-1] {
count++
} else {
t += strconv.Itoa(count) + string(s[j-1])
count = 1
}
}
t += strconv.Itoa(count) + string(s[len(s)-1])
s = t
}
return s
}


🪙 349 вопроса вопроса на Golang разработчика

🔒 База собесов | 🔒 База тестовых
Please open Telegram to view this post
VIEW IN TELEGRAM
1🤔1