Алгоритмы - Массивы
Этот код решает задачу нахождения длины самой длинной непрерывной подпоследовательности одинаковых элементов в массиве.
Алгоритм решения
- Инициализируем переменную maxLength для хранения максимальной длины.
- Проходим по массиву, начиная с первого элемента до предпоследнего.
- Для каждого элемента:
- Устанавливаем левый указатель (left) на текущий элемент.
- Устанавливаем правый указатель (right) на следующий элемент.
- Двигаем правый указатель вправо, пока встречаются элементы, равные текущему.
- Вычисляем длину найденной подпоследовательности (right - left).
- Обновляем maxLength, если найденная длина больше.
- Перемещаем индекс i на конец найденной подпоследовательности минус 1.
- Возвращаем maxLength.
Временная сложность: O(n), где n - длина массива. Несмотря на вложенный цикл, каждый элемент массива просматривается не более двух раз: один раз как начало последовательности и один раз как её продолжение.
Пространственная сложность: O(1), так как используется только фиксированное количество дополнительных переменных, независимо от размера входного массива.
func findMaxLength(arr []int) int {
var maxLength int
for i := 0; i < len(arr)-1; i++ {
left, right := i, i+1
for right < len(arr) && arr[left] == arr[right] {
right++
}
maxLength = max(maxLength, right-left)
i = right - 1
}
return maxLength
}
Этот код реализует функцию intersect
, которая находит пересечение двух
целочисленных массивов nums1
и nums2
. Пересечение включает все
элементы, которые присутствуют в обоих массивах, с учетом их количества.
Алгоритм решения
- Создание хеш-таблицы (map) для подсчета элементов первого массива:
- Проходим по
nums1
и увеличиваем счетчик для каждого элемента в map.
- Проходим по
- Поиск пересечения:
- Проходим по
nums2
. - Если элемент присутствует в map и его счетчик больше 0:
- Добавляем элемент в результат.
- Уменьшаем счетчик элемента в map.
- Проходим по
- Возвращаем результирующий массив.
- Временная сложность: O(n + m)
- n - длина nums1
- m - длина nums2
- Требуется один проход по каждому массиву
- Пространственная сложность: O(min(n, m))
- Для хранения map и результирующего массива
- В худшем случае размер равен длине меньшего из двух массивов
func intersect(nums1 []int, nums2 []int) []int {
// Создаем map для подсчета элементов в nums1
count := make(map[int]int)
for _, num := range nums1 {
count[num]++
}
// Создаем слайс для результата
result := []int{}
// Проходим по nums2 и проверяем наличие элементов в map
for _, num := range nums2 {
if count[num] > 0 {
result = append(result, num)
count[num]--
}
}
return result
}
Этот код реализует функцию merge
, которая объединяет два отсортированных массива
nums1
и nums2
в один отсортированный массив. Результат сохраняется
в массиве nums1
, который изначально имеет достаточно места для хранения всех
элементов.
Алгоритм решения
- Инициализация указателей:
p1
: указывает на последний элемент вnums1
p2
: указывает на последний элемент вnums2
p
: указывает на последнюю позицию в объединенном массиве
- Итерация с конца массивов:
- Сравниваем элементы
nums1[p1]
иnums2[p2]
- Больший элемент помещаем в позицию
p
массиваnums1
- Сдвигаем соответствующий указатель и
p
- Сравниваем элементы
- Продолжаем, пока не обработаем все элементы из
nums2
- Временная сложность: O(m+n)
- m - количество элементов в nums1
- n - количество элементов в nums2
- Алгоритм проходит по всем элементам обоих массивов один раз
- Пространственная сложность: O(1)
- Алгоритм работает in-place, не используя дополнительной памяти
func merge(nums1 []int, m int, nums2 []int, n int) {
// Указатели на последние элементы массивов
p1 := m - 1 // для nums1
p2 := n - 1 // для nums2
p := m + n - 1 // для объединенного массива
// Идем с конца массивов, заполняя nums1 с конца
for p2 >= 0 {
if p1 >= 0 && nums1[p1] > nums2[p2] {
nums1[p] = nums1[p1]
p1--
} else {
nums1[p] = nums2[p2]
p2--
}
p--
}
}
Этот код реализует функцию missingNumber
, которая находит пропущенное число в
последовательности от 0 до n, где n - длина входного массива nums
.
Предполагается, что в массиве nums
содержатся все числа от 0 до n, кроме
одного.
Алгоритм решения
- Инициализация переменных:
sum
: сумма всех чисел от 0 до n (включительно)res
: сумма всех чисел в массивеnums
- Итерация по массиву:
- Добавляем i к
sum
(учитывая числа от 0 до n-1) - Добавляем
nums[i]
кres
- Добавляем i к
- Вычисление пропущенного числа:
- Возвращаем разницу между
sum
иres
- Возвращаем разницу между
- Временная сложность: O(n)
- Где n - длина входного массива
- Алгоритм проходит по массиву один раз
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительной памяти
func missingNumber(nums []int) int {
sum := len(nums)
res := 0
for i := 0; i < len(nums); i++ {
sum += i
res += nums[i]
}
return sum - res
}
Этот код реализует функцию moveZeroes
, которая перемещает все нули в конец
массива nums
, сохраняя относительный порядок ненулевых элементов.
Алгоритм решения
- Инициализация указателя
nonZeroIndex
:- Указывает на позицию, куда нужно поместить следующий ненулевой элемент
- Первый проход по массиву:
- Если элемент не равен нулю, перемещаем его на позицию
nonZeroIndex
- Увеличиваем
nonZeroIndex
- Если элемент не равен нулю, перемещаем его на позицию
- Второй проход по оставшейся части массива:
- Заполняем все позиции от
nonZeroIndex
до конца массива нулями
- Заполняем все позиции от
- Временная сложность: O(n)
- Где n - длина входного массива
- Алгоритм проходит по массиву дважды: один раз для перемещения ненулевых элементов и второй раз для заполнения нулями
- Пространственная сложность: O(1)
- Алгоритм работает in-place, не используя дополнительной памяти
func moveZeroes(nums []int) {
nonZeroIndex := 0
// Перемещаем все ненулевые элементы в начало массива
for i := 0; i < len(nums); i++ {
if nums[i] != 0 {
nums[nonZeroIndex] = nums[i]
nonZeroIndex++
}
}
// Заполняем оставшуюся часть массива нулями
for i := nonZeroIndex; i < len(nums); i++ {
nums[i] = 0
}
}
Функция sortedSquares
принимает отсортированный массив целых чисел (возможно,
содержащий отрицательные числа) и возвращает новый массив, содержащий квадраты этих чисел в
отсортированном порядке.
Алгоритм решения
- Инициализация:
- Создаем результирующий массив той же длины, что и входной
- Устанавливаем два указателя:
left
в начало иright
в конец входного массива
- Итерация с конца результирующего массива:
- Сравниваем абсолютные значения чисел на левом и правом указателях
- Больший квадрат помещаем в текущую позицию результата
- Сдвигаем соответствующий указатель
- Повторяем, пока не заполним весь результирующий массив
- Временная сложность: O(n)
- Где n - длина входного массива
- Алгоритм проходит по всем элементам ровно один раз
- Пространственная сложность: O(n)
- Создается новый массив для хранения результата, длина которого равна длине входного массива
func sortedSquares(nums []int) []int {
n := len(nums)
result := make([]int, n)
left, right := 0, n-1
for i := n - 1; i >= 0; i-- {
if abs(nums[left]) > abs(nums[right]) {
result[i] = nums[left] * nums[left]
left++
} else {
result[i] = nums[right] * nums[right]
right--
}
}
return result
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
Функция summaryRanges
принимает отсортированный массив целых чисел и возвращает
список строк, представляющих диапазоны последовательных чисел в этом массиве.
Алгоритм решения
- Проверка на пустой массив:
- Если массив пуст, возвращаем пустой список строк
- Инициализация:
- Создаем пустой список строк для результата
- Инициализируем пустую строку
current
для хранения начала текущего диапазона
- Итерация по массиву:
- Для каждого числа проверяем три случая:
- Не последний элемент и нет текущего диапазона
- Не последний элемент и есть текущий диапазон
- Последний элемент массива
- В зависимости от случая, либо начинаем новый диапазон, либо завершаем текущий, либо добавляем одиночное число
- Для каждого числа проверяем три случая:
- Формирование результата:
- Добавляем сформированные диапазоны или одиночные числа в результирующий список
- Временная сложность: O(n)
- Где n - длина входного массива
- Алгоритм проходит по всем элементам массива ровно один раз
- Пространственная сложность: O(n)
- В худшем случае (когда каждое число образует отдельный диапазон) результирующий список будет содержать n элементов
func summaryRanges(nums []int) []string {
if len(nums) == 0 {
return []string{}
}
var res []string
current := ""
for i, num := range nums {
switch {
case i < len(nums)-1 && current == "":
if num+1 == nums[i+1] {
current = strconv.Itoa(num)
} else {
res = append(res, strconv.Itoa(num))
}
case i < len(nums)-1 && current != "":
if num+1 != nums[i+1] {
res = append(res, current+"->"+strconv.Itoa(num))
current = ""
}
case i == len(nums)-1:
if current != "" {
res = append(res, current+"->"+strconv.Itoa(num))
} else {
res = append(res, strconv.Itoa(num))
}
}
}
return res
}
Функция twoSum
находит два числа в массиве, сумма которых равна заданному
целевому значению (target). Возвращает индексы этих двух чисел.
Алгоритм решения
- Инициализация:
- Создаем пустую хеш-таблицу (map) для хранения чисел и их индексов
- Итерация по массиву:
- Для каждого числа вычисляем его дополнение (complement) до целевого значения
- Проверяем, есть ли дополнение в хеш-таблице
- Если есть, возвращаем индексы текущего числа и его дополнения
- Если нет, добавляем текущее число и его индекс в хеш-таблицу
- Если решение не найдено:
- Возвращаем nil
- Временная сложность: O(n)
- Где n - количество элементов в массиве
- Алгоритм проходит по массиву только один раз
- Пространственная сложность: O(n)
- В худшем случае хеш-таблица может содержать все n элементов массива
func twoSum(nums []int, target int) []int {
m := make(map[int]int)
for i, num := range nums {
complement := target - num
if j, ok := m[complement]; ok {
return []int{j, i}
}
m[num] = i
}
return nil
}
Дан отсортированный по возрастанию массив целых чисел 'a' и целевое значение 'target'. Необходимо найти два индекса 0 ≤ l < r < n, такие что a[l] + a[r] == target. Если такая пара существует, нужно вернуть эти индексы. В противном случае вернуть признак отсутствия решения.
Алгоритм решения
- Инициализировать два указателя: left (в начале массива) и right (в конце массива).
- Пока left < right:
- Вычислить сумму элементов a[left] и a[right].
- Если сумма равна target, вернуть индексы left и right.
- Если сумма меньше target, увеличить left.
- Если сумма больше target, уменьшить right.
- Если цикл завершился без нахождения решения, вернуть признак отсутствия решения.
- Временная сложность: O(n), где n - длина массива.
- Пространственная сложность: O(1), используется константное дополнительное пространство.
func twoSum(nums []int, target int) (int, int) {
left, right := 0, len(nums)-1
for left < right {
sum := nums[left] + nums[right]
if sum == target {
return left, right
} else if sum < target {
left++
} else {
right--
}
}
// Если пара не найдена, возвращаем недопустимые индексы
return -1, -1
}
Функция evalRPN
вычисляет результат арифметического выражения, записанного в
обратной польской нотации (RPN). В этой нотации операнды располагаются перед операторами.
Алгоритм решения
- Инициализация:
- Создаем пустой стек для хранения чисел
- Итерация по токенам:
- Если токен - число, добавляем его в стек
- Если токен - оператор:
- Извлекаем два последних числа из стека
- Выполняем соответствующую операцию
- Результат помещаем обратно в стек
- Завершение:
- Возвращаем единственное число, оставшееся в стеке (результат вычисления)
- Временная сложность: O(n)
- Где n - количество токенов в входном массиве
- Алгоритм проходит по каждому токену ровно один раз
- Пространственная сложность: O(n)
- В худшем случае стек может содержать все числа из входного массива
func evalRPN(tokens []string) int {
stack := make([]int, 0)
for _, token := range tokens {
if num, err := strconv.Atoi(token); err == nil {
stack = append(stack, num)
continue
}
a, b := stack[len(stack)-2], stack[len(stack)-1]
stack = stack[:len(stack)-2]
switch token {
case "+":
stack = append(stack, a+b)
case "-":
stack = append(stack, a-b)
case "*":
stack = append(stack, a*b)
case "/":
stack = append(stack, a/b)
}
}
return stack[0]
}
Функция searchRange
находит начальную и конечную позиции заданного целевого
значения в отсортированном массиве. Если целевое значение не найдено, возвращается [-1, -1].
Алгоритм решения
- Проверка на пустой массив:
- Если массив пуст, возвращаем [-1, -1]
- Бинарный поиск:
- Инициализируем указатели left и right на начало и конец массива
- Выполняем бинарный поиск, пока left ≤ right
- Если найдено целевое значение:
- Расширяем диапазон влево и вправо, пока находим целевое значение
- Возвращаем найденный диапазон
- Если значение в середине меньше цели, сдвигаем left
- Если значение в середине больше цели, сдвигаем right
- Если целевое значение не найдено, возвращаем [-1, -1]
- Временная сложность:
- В лучшем и среднем случае: O(log n) - благодаря бинарному поиску
- В худшем случае: O(n) - когда все элементы равны целевому значению
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительной памяти
func searchRange(nums []int, target int) []int {
if len(nums) == 0 {
return []int{-1, -1}
}
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
// считаем элементы слева и справа
l, r := mid, mid
for {
if l >= 0 && nums[l] == target {
l--
}
if r < len(nums) && nums[r] == target {
r++
}
if (l < 0 || nums[l] != target) && (r == len(nums) || nums[r] != target) {
return []int{l + 1, r - 1}
}
}
}
if nums[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return []int{-1, -1}
}
Функция findClosestElements
находит K ближайших элементов к заданному числу X в
отсортированном массиве.
Алгоритм решения
- Проверка граничных условий:
- Если длина массива меньше K или массив пуст, возвращаем весь массив
- Если X меньше или равно первому элементу, возвращаем первые K элементов
- Если X больше или равно последнему элементу, возвращаем последние K элементов
- Бинарный поиск:
- Инициализируем левую границу поиска как 0, правую как len(arr) - K
- Выполняем бинарный поиск, пока left < right:
- Находим середину диапазона
- Сравниваем расстояния от X до элементов arr[mid] и arr[mid+K]
- Сдвигаем границы поиска в зависимости от результата сравнения
- Возвращаем подмассив длины K, начиная с найденной позиции
- Временная сложность: O(log(n-k) + k)
- log(n-k) - для бинарного поиска
- k - для формирования результирующего массива
- Пространственная сложность: O(k)
- Для хранения результирующего массива
func findClosestElements(arr []int, k int, x int) []int {
if len(arr) < k || len(arr) == 0 {
return arr
}
left, right := 0, len(arr)-k // ставим окно поиска
// Проверка граничных случаев
if x <= arr[0] { // 0 элемент больше x то берём первые k элементов
return arr[:k]
}
if x >= arr[len(arr)-1] { // если последний элемент больше x, то берём последние k элементов
return arr[len(arr)-k:]
}
// Бинарный поиск
for left < right {
mid := left + (right-left)/2
if x-arr[mid] > arr[mid+k]-x { // если x больше среднего, двигаем в правую часть
left = mid + 1
} else {
right = mid
}
}
return arr[left : left+k]
}
Функция groupAnagrams
группирует анаграммы из заданного списка строк. Анаграммы
- это слова, состоящие из одних и тех же букв, но в разном порядке.
Алгоритм решения
- Инициализация:
- Создаем пустую карту (map) для хранения групп анаграмм
- Обработка каждой строки:
- Преобразуем строку в слайс рун
- Сортируем символы строки
- Используем отсортированную строку как ключ в карте
- Добавляем исходную строку в соответствующую группу в карте
- Формирование результата:
- Преобразуем карту в слайс слайсов строк
- Возвращаем результат
- Временная сложность: O(n * k * log(k))
- n - количество строк в исходном слайсе
- k - максимальная длина строки
- Сортировка каждой строки занимает O(k * log(k))
- Пространственная сложность: O(n * k)
- Для хранения всех строк в карте и результирующем слайсе
func groupAnagrams(strs []string) [][]string {
groups := make(map[string][]string)
for _, str := range strs {
// Сортируем символы строки для получения ключа
chars := []rune(str)
slices.Sort(chars)
key := string(chars)
// Добавляем строку в соответствующую группу
groups[key] = append(groups[key], str)
}
// Преобразуем map в slice
result := make([][]string, 0, len(groups))
for _, group := range groups {
result = append(result, group)
}
return result
}
Функция longestSubarray
находит длину самого длинного подмассива, состоящего
только из единиц, после удаления одного элемента из исходного массива.
Алгоритм решения
- Инициализация переменных:
left
- левая граница окнаzeros
- количество нулей в текущем окнеmaxLen
- максимальная длина найденного подмассива
- Проход по массиву с использованием техники "скользящего окна":
- Расширяем окно вправо
- Если встречаем ноль, увеличиваем счетчик нулей
- Если в окне больше одного нуля, сужаем окно слева
- Обновляем максимальную длину подмассива
- Обработка особого случая:
- Если максимальная длина равна длине исходного массива, вычитаем 1
- Возвращаем результат
- Временная сложность: O(n)
- n - количество элементов в массиве
- Каждый элемент обрабатывается не более двух раз (при расширении и сужении окна)
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительной памяти
func longestSubarray(nums []int) int {
left, zeros, maxLen := 0, 0, 0
for right := 0; right < len(nums); right++ {
if nums[right] == 0 {
zeros++
}
// Сужаем окно, если в нем больше одного нуля
for zeros > 1 {
if nums[left] == 0 {
zeros--
}
left++
}
// Обновляем максимальную длину
// Вычитаем 1, так как нужно удалить один элемент
maxLen = max(maxLen, right-left)
}
// Если maxLen равен длине массива, возвращаем maxLen-1,
// иначе возвращаем maxLen
if maxLen == len(nums) {
return maxLen - 1
}
return maxLen
}
Функция longestOnes
находит длину самого длинного подмассива, состоящего только
из единиц, после замены не более k нулей на единицы.
Алгоритм решения
- Инициализация переменных:
left
- левая граница окнаmaxOnes
- максимальная длина найденного подмассива
- Проход по массиву с использованием техники "скользящего окна":
- Расширяем окно вправо
- Если встречаем ноль, уменьшаем k
- Если k становится отрицательным, сдвигаем левую границу окна
- Обновляем максимальную длину подмассива
- Возвращаем результат
- Временная сложность: O(n)
- n - количество элементов в массиве
- Каждый элемент обрабатывается не более двух раз (при расширении и сужении окна)
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительной памяти
func longestOnes(nums []int, k int) int {
left, maxOnes := 0, 0
for right := 0; right < len(nums); right++ {
if nums[right] == 0 {
k--
}
if k < 0 {
if nums[left] == 0 {
k++
}
left++
}
maxOnes = max(maxOnes, right-left+1)
}
return maxOnes
}
Функция maxDistToClosest
находит максимальное расстояние, на котором можно сесть
от ближайшего человека в ряду сидений, представленном массивом seats
, где 1
означает занятое место, а 0 - свободное.
Алгоритм решения
- Инициализация переменных:
n
- длина массива seatsleft
- индекс последнего занятого места слева (изначально -1)maxDist
- максимальное найденное расстояние
- Проход по массиву seats:
- Если встречаем занятое место (1):
- Если это первое занятое место, проверяем расстояние от начала ряда
- Иначе вычисляем расстояние между текущим и предыдущим занятым местом
- Обновляем
maxDist
, если найдено большее расстояние - Обновляем
left
- Если встречаем занятое место (1):
- После прохода проверяем расстояние от последнего занятого места до конца ряда
- Возвращаем максимальное найденное расстояние
- Временная сложность: O(n)
- n - длина массива seats
- Алгоритм проходит по массиву ровно один раз
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительных переменных
func maxDistToClosest(seats []int) int {
n := len(seats)
left := -1 // индекс последнего занятого места слева
maxDist := 0
for i := 0; i < n; i++ {
if seats[i] == 1 {
// Если это первое занятое место, проверяем расстояние от начала
if left == -1 {
maxDist = i
} else {
// Иначе проверяем расстояние между двумя занятыми местами
maxDist = max(maxDist, (i-left)/2)
}
left = i
}
}
// Проверяем расстояние от последнего занятого места до конца
maxDist = max(maxDist, n-1-left)
return maxDist
}
Функция findMedianSortedArrays
находит медиану двух отсортированных массивов
nums1
и nums2
.
Алгоритм решения
- Обработка краевых случаев:
- Если один из массивов пустой, возвращается медиана другого массива
- Оптимизация:
- Обеспечение того, что
nums1
- более короткий массив
- Обеспечение того, что
- Бинарный поиск по более короткому массиву:
- Поиск правильной точки разделения в обоих массивах
- Сравнение элементов на границах разделения
- Корректировка точки разделения в зависимости от сравнения
- Вычисление медианы:
- Для четного общего количества элементов - среднее двух средних элементов
- Для нечетного - максимальный из двух "левых" элементов
- Временная сложность: O(log(min(m, n)))
- m и n - длины массивов nums1 и nums2 соответственно
- Бинарный поиск выполняется по более короткому массиву
- Пространственная сложность: O(1)
- Используется постоянное количество дополнительной памяти
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
// Обработка случая, когда один из массивов пустой
if len(nums1) == 0 {
return medianOfArray(nums2)
}
if len(nums2) == 0 {
return medianOfArray(nums1)
}
// Мы всегда работаем с меньшим массивом как с nums1 для оптимизации.
if len(nums1) > len(nums2) {
nums1, nums2 = nums2, nums1
}
l1, l2 := len(nums1), len(nums2)
// бинарный поиск
left, right := 0, l1
for left <= right {
cur1 := (left + right) / 2
cur2 := (l1+l2+1)/2 - cur1
/* Вычисляем соответствующую точку разделения для второго массива: cur2 = (l1 + l2 + 1) / 2 - cur1
Это обеспечивает, что левая часть обоих массивов всегда содержит (l1 + l2 + 1) / 2 элементов */
first1 := getVal(nums1, cur1, math.MaxInt)
last1 := getVal(nums1, cur1-1, math.MinInt)
first2 := getVal(nums2, cur2, math.MaxInt)
last2 := getVal(nums2, cur2-1, math.MinInt)
switch {
case last1 <= first2 && last2 <= first1:
// чётное кол-во или нет
if (l1+l2)%2 == 0 {
return (float64(max(last1, last2)) + float64(min(first1, first2))) / 2.0
}
return float64(max(last1, last2))
case last1 > first2:
right = cur1 - 1
default:
left = cur1 + 1
}
}
return 0.0
}
/*
*
Функция getVal используется для безопасного получения этих значений,
возвращая минимальное или максимальное целое число, если индекс выходит за границы массива.
*/
func getVal(nums []int, index int, defaultVal int) int {
if index < 0 || index >= len(nums) {
return defaultVal
}
return nums[index]
}
func medianOfArray(nums []int) float64 {
n := len(nums)
if n%2 == 0 {
return float64(nums[n/2-1]+nums[n/2]) / 2.0
}
return float64(nums[n/2])
}
Функция findNonOverlappingIntervals
принимает массив интервалов и возвращает
новый массив, где все пересекающиеся интервалы объединены.
Алгоритм решения
- Проверка на пустой входной массив
- Сортировка интервалов по начальной точке (при равенстве - по конечной точке)
- Итерация по отсортированным интервалам:
- Если текущий интервал пересекается с предыдущим, объединяем их
- Если не пересекается, добавляем предыдущий интервал в результат и начинаем новый
- Добавление последнего обработанного интервала в результат
- Временная сложность: O(n log n)
- Сортировка интервалов занимает O(n log n)
- Последующая итерация по интервалам занимает O(n)
- Пространственная сложность: O(n)
- В худшем случае, когда нет пересечений, результирующий массив будет такого же размера, как входной
func findNonOverlappingIntervals(intervals [][]int) [][]int {
if len(intervals) == 0 {
return [][]int{}
}
slices.SortFunc(intervals, func(i, j []int) int { // log n
switch {
case i[0] == j[0] && i[1] == j[1]:
return 0
case i[0] < j[0] || (i[0] == j[0] && i[1] < j[1]):
return -1
default:
return 1
}
})
var resultIntervals [][]int
currentInterval := intervals[0]
for _, interval := range intervals[1:] {
switch {
case interval[0] <= currentInterval[1]: // [1, 3] in [2, 6]
currentInterval[1] = max(currentInterval[1], interval[1])
case interval[0] > currentInterval[1]: // [1, 3] in [5, 7]
resultIntervals = append(resultIntervals, currentInterval)
currentInterval = interval
}
}
resultIntervals = append(resultIntervals, currentInterval)
return resultIntervals
}
Функция MergeKSortedArrays
объединяет K отсортированных массивов в один
отсортированный массив.
Алгоритм решения
- Инициализация результирующего массива и массива указателей для каждого входного массива
- Итеративный процесс:
- Поиск минимального элемента среди текущих элементов всех массивов
- Добавление найденного минимального элемента в результат
- Продвижение указателя для массива, из которого был взят минимальный элемент
- Повторение процесса, пока все элементы не будут обработаны
- Временная сложность: O(N * k)
- N - общее количество элементов во всех массивах
- k - количество массивов
- На каждой итерации происходит поиск минимума среди k элементов
- Пространственная сложность: O(N + k)
- O(N) для результирующего массива
- O(k) для массива указателей
func MergeKSortedArrays(arrays [][]int) []int {
result := make([]int, 0)
points := make([]int, len(arrays))
for {
minVal := 101
minIdx := -1
// Ищем минимальный индекс
for i, arr := range arrays {
if points[i] < len(arr) &&
(minIdx < 0 || arr[points[i]] < minVal) {
minVal = arr[points[i]]
minIdx = i
}
}
// Если не нашли минимальный элемент, значит все массивы обработаны
if minIdx == -1 {
break
}
// Добавляем минимальный элемент в результат и двигаем указатель
result = append(result, minVal)
points[minIdx]++
}
return result
}
Функция productExceptSelf
вычисляет для каждого элемента массива произведение
всех остальных элементов, не используя операцию деления.
Алгоритм решения
- Инициализация результирующего массива
answer
- Вычисление префиксных произведений:
- Проход слева направо
- Каждый элемент
answer[i]
содержит произведение всех элементов слева отnums[i]
- Вычисление суффиксных произведений и формирование окончательного результата:
- Проход справа налево
- Умножение каждого элемента
answer[i]
на суффиксное произведение - Обновление суффиксного произведения
- Временная сложность: O(n)
- Два прохода по массиву: один слева направо, другой справа налево
- Пространственная сложность: O(1)
- Не считая выходной массив, используется только одна дополнительная переменная
func productExceptSelf(nums []int) []int {
n := len(nums)
answer := make([]int, n)
// Инициализируем первый элемент answer как 1
answer[0] = 1
// Вычисляем префиксное произведение
for i := 1; i < n; i++ {
answer[i] = answer[i-1] * nums[i-1]
}
// Вычисляем суффиксное произведение и одновременно обновляем answer
suffixProduct := 1
for i := n - 1; i >= 0; i-- {
answer[i] *= suffixProduct
suffixProduct *= nums[i]
}
return answer
}
Функция search
выполняет поиск заданного элемента target
в
повернутом отсортированном массиве nums
.
Алгоритм решения
- Инициализация указателей
left
иright
на начало и конец массива - Бинарный поиск с модификацией:
- Нахождение среднего элемента
mid
- Если
nums[mid] == target
, возвращаемmid
- Определение, какая половина массива отсортирована:
- Если левая половина отсортирована (
nums[left] <= nums[mid]
):- Если
target
находится в левой половине, сужаем поиск влево - Иначе сужаем поиск вправо
- Если
- Если правая половина отсортирована:
- Если
target
находится в правой половине, сужаем поиск вправо - Иначе сужаем поиск влево
- Если
- Если левая половина отсортирована (
- Нахождение среднего элемента
- Если элемент не найден, возвращаем -1
- Временная сложность: O(log n)
- Используется модифицированный бинарный поиск
- На каждой итерации область поиска уменьшается вдвое
- Пространственная сложность: O(1)
- Используется фиксированное количество дополнительных переменных
func search(nums []int, target int) int {
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
}
// Левая половина отсортирована
if nums[left] <= nums[mid] {
if target >= nums[left] && target < nums[mid] {
right = mid - 1
} else {
left = mid + 1
}
} else { // Правая половина отсортирована
if target > nums[mid] && target <= nums[right] {
left = mid + 1
} else {
right = mid - 1
}
}
}
return -1 // Элемент не найден
}
Функция subarraySum
подсчитывает количество подмассивов в заданном массиве
nums
, сумма элементов которых равна заданному значению k
.
Алгоритм решения
- Инициализация переменных:
count
- счетчик подходящих подмассивовsum
- текущая кумулятивная суммаsumCounts
- карта для хранения количества встреченных кумулятивных сумм
- Итерация по элементам массива:
- Обновление кумулятивной суммы
- Проверка наличия комплементарной суммы
sum-k
в карте - Увеличение счетчика на количество найденных комплементарных сумм
- Обновление количества текущей кумулятивной суммы в карте
- Возврат общего количества найденных подмассивов
- Временная сложность: O(n)
- n - количество элементов в массиве
- Один проход по всем элементам массива
- Пространственная сложность: O(n)
- В худшем случае, когда все кумулятивные суммы уникальны
- Использование карты для хранения кумулятивных сумм
func subarraySum(nums []int, k int) int {
count := 0
sum := 0
sumCounts := make(map[int]int)
sumCounts[0] = 1 // Инициализируем для случая, когда подмассив начинается с начала массива
for _, num := range nums {
sum += num
if cnt, exists := sumCounts[sum-k]; exists {
count += cnt
}
sumCounts[sum]++
}
return count
}
Функция subarraysDivByK
подсчитывает количество подмассивов в заданном массиве
nums
, сумма элементов которых делится на заданное число k
.
Алгоритм решения
- Инициализация переменных:
count
- карта для хранения количества встреченных остатков от деленияsum
- текущая кумулятивная суммаresult
- счетчик подходящих подмассивов
- Итерация по элементам массива:
- Обновление кумулятивной суммы
- Вычисление остатка от деления суммы на k (с обработкой отрицательных чисел)
- Увеличение результата на количество ранее встреченных подмассивов с таким же остатком
- Обновление количества текущего остатка в карте
- Возврат общего количества найденных подмассивов
- Временная сложность: O(n)
- n - количество элементов в массиве
- Один проход по всем элементам массива
- Пространственная сложность: O(min(n, k))
- В худшем случае хранятся k различных остатков в карте
- Количество уникальных остатков ограничено значением k
func subarraysDivByK(nums []int, k int) int {
count := make(map[int]int)
count[0] = 1 // Инициализируем для случая, когда вся префиксная сумма делится на k
sum := 0
result := 0
for _, num := range nums {
sum += num
remainder := (sum%k + k) % k // Обрабатываем отрицательные числа
result += count[remainder]
count[remainder]++
}
return result
}
Дан массив целых чисел. Необходимо найти в нем такой индекс, что сумма элементов слева от него равна сумме элементов справа от него. Если такого индекса нет, вернуть -1. Важно решить задачу за O(N) времени и за O(1) памяти.
Примеры:
Входной массив: [1, 7, 3, 6, 5, 6]
Результат: 3
(сумма слева: 1+7+3=11, сумма справа: 5+6=11)
Входной массив: [1, 2, 3]
Результат: -1
(нет индекса равновесия)
Алгоритм решения
- Проверяем, что массив не пуст. Если массив пуст, возвращаем -1.
- Вычисляем общую сумму всех элементов массива.
- Инициализируем переменную leftSum = 0 для хранения суммы элементов слева от текущего индекса.
- Проходим по массиву и для каждого индекса i:
- Вычисляем сумму элементов справа как rightSum = totalSum - leftSum - nums[i]
- Проверяем, равна ли сумма элементов слева (leftSum) сумме элементов справа (rightSum)
- Если равна, возвращаем текущий индекс i
- Обновляем leftSum, добавляя к ней текущий элемент для следующей итерации
- Если после проверки всех индексов точка равновесия не найдена, возвращаем -1.
Временная сложность: O(N), где N - длина массива.
Мы делаем два прохода по массиву: один для вычисления общей суммы и один для поиска индекса равновесия. Оба прохода линейны, поэтому общая временная сложность составляет O(N).
Пространственная сложность: O(1).
Мы используем только несколько переменных (totalSum, leftSum, rightSum) независимо от размера входного массива, поэтому пространственная сложность постоянна.
func FindEquilibriumIndex(nums []int) int {
n := len(nums)
if n == 0 {
return -1
}
// Вычисляем общую сумму всех элементов
totalSum := 0
for _, num := range nums {
totalSum += num
}
// Проходим по массиву и проверяем каждый индекс
leftSum := 0
for i := 0; i < n; i++ {
// Правая сумма = общая сумма - левая сумма - текущий элемент
rightSum := totalSum - leftSum - nums[i]
// Если левая сумма равна правой, нашли индекс равновесия
if leftSum == rightSum {
return i
}
// Обновляем левую сумму для следующей итерации
leftSum += nums[i]
}
// Если индекс равновесия не найден
return -1
}
Дан массив целых чисел. Необходимо найти максимальный по длине подотрезок (непрерывный подмассив), в котором сумма всех элементов является четным числом. Если таких подотрезков несколько, нужно выбрать наибольший по длине. Если подотрезков с четной суммой нет, вернуть 0.
Примеры:
Входной массив: [1, 2, 3, 4, 5]
Результат: 4
(подотрезок [2, 3, 4, 5] имеет сумму 14, которая четная)
Входной массив: [2, 4, 6, 8]
Результат: 4
(весь массив имеет четную сумму 20)
Входной массив: [1, 3, 5, 7]
Результат: 0
(нет подотрезков с четной суммой)
Алгоритм решения
Алгоритм основан на следующем математическом наблюдении: если префиксные суммы двух подмассивов имеют одинаковый остаток при делении на 2, то сумма элементов между ними будет четной.
- Проверяем, что массив не пуст. Если массив пуст, возвращаем 0.
- Создаем карту для хранения индекса первого вхождения каждого остатка от деления префиксной суммы на 2.
- Инициализируем карту с остатком 0 и индексом -1 (это нужно для обработки случая, когда префиксная сумма сама по себе четная).
- Проходим по массиву, накапливая текущую сумму и вычисляя остаток от деления на 2.
- Для каждой позиции проверяем:
- Если текущий остаток уже встречался ранее, значит между текущей позицией и первым вхождением этого остатка сумма четная.
- Вычисляем длину этого подотрезка и обновляем максимальную длину.
- Если остаток встречается впервые, запоминаем его позицию.
- Возвращаем найденную максимальную длину.
Временная сложность: O(N), где N - длина массива.
Мы делаем один проход по массиву, и все операции внутри цикла (доступ к карте, вычисление остатка) выполняются за O(1).
Пространственная сложность: O(1).
Несмотря на использование карты, в ней будет максимум 2 элемента (для остатков 0 и 1), поэтому пространственная сложность константна.
func FindMaxEvenSubsegment(nums []int) int {
n := len(nums)
if n == 0 {
return 0
}
// Храним индекс первого вхождения остатка от деления на 2
// Для четной суммы (остаток 0) и нечетной суммы (остаток 1)
firstOccurrence := map[int]int{
0: -1, // Инициализируем для четной суммы как -1 (до начала массива)
}
maxLength := 0
currentSum := 0
for i := 0; i < n; i++ {
// Обновляем текущую сумму и вычисляем остаток от деления на 2
currentSum += nums[i]
remainder := currentSum % 2
// Если такой остаток уже встречался, значит между текущей позицией
// и первым вхождением этого остатка сумма четная
if idx, exists := firstOccurrence[remainder]; exists {
length := i - idx
maxLength = max(maxLength, length)
} else {
// Запоминаем первое вхождение этого остатка
firstOccurrence[remainder] = i
}
}
return maxLength
}
Дан массив целых положительных чисел и число k. Требуется найти максимальный по длине подмассив, в котором разница между максимальным и минимальным элементом не превышает k.
Алгоритм решения
- Инициализируем два указателя: left и right, определяющие текущий подмассив.
- Создаем два монотонных стека: minStack для минимальных элементов и maxStack для максимальных.
- Двигаем правый указатель right по массиву:
- Обновляем minStack, удаляя элементы, большие текущего.
- Обновляем maxStack, удаляя элементы, меньшие текущего.
- Добавляем текущий индекс в оба стека.
- Если разница между максимальным и минимальным элементом превышает k:
- Двигаем левый указатель left вправо.
- Обновляем стеки, удаляя элементы, вышедшие за пределы текущего окна.
- Обновляем максимальную длину подмассива.
- Повторяем шаги 3-5, пока не достигнем конца массива.
- Использование монотонных стеков позволяет эффективно отслеживать минимальные и максимальные элементы в текущем окне.
- Алгоритм работает за линейное время, что делает его эффективным для больших массивов.
- Решение использует технику "скользящего окна" с динамическим изменением его размера.
- Подход позволяет решить задачу за один проход по массиву.
- Временная сложность: O(n), где n - длина входного массива. Каждый элемент добавляется и удаляется из стеков не более одного раза.
- Пространственная сложность: O(n) в худшем случае, когда все элементы могут оказаться в стеках.
func maxSubarrayWithDiffK(arr []int, k int) int {
n := len(arr)
maxLen := 0
left := 0
minStack := []int{}
maxStack := []int{}
for right := 0; right < n; right++ {
// Обновляем минимальный стек
for len(minStack) > 0 && arr[minStack[len(minStack)-1]] > arr[right] {
minStack = minStack[:len(minStack)-1]
}
minStack = append(minStack, right)
// Обновляем максимальный стек
for len(maxStack) > 0 && arr[maxStack[len(maxStack)-1]] < arr[right] {
maxStack = maxStack[:len(maxStack)-1]
}
maxStack = append(maxStack, right)
// Проверяем разницу между максимальным и минимальным элементом
for arr[maxStack[0]]-arr[minStack[0]] > k {
left++
if left > minStack[0] {
minStack = minStack[1:]
}
if left > maxStack[0] {
maxStack = maxStack[1:]
}
}
// Обновляем максимальную длину
maxLen = max(maxLen, right-left+1)
}
return maxLen
}
Дан массив целых чисел nums
и целевое значение target
. Необходимо
найти такую тройку
чисел в массиве с различными индексами, что их сумма наиболее близка к целевому значению
target
.
Требуется вернуть саму сумму трех чисел.
Примеры:
Пример 1:
Вход: nums = [-1, 2, 1, -4]
, target = 1
Выход: 2
Пояснение: Сумма -1 + 2 + 1 = 2
наиболее близка к целевому значению 1.
Пример 2:
Вход: nums = [0, 0, 0]
, target = 1
Выход: 0
Пояснение: Сумма 0 + 0 + 0 = 0
наиболее близка к целевому значению 1.
Алгоритм решения
Для решения этой задачи используется подход с сортировкой массива и техникой двух указателей:
- Проверяем, что в массиве достаточно элементов (не менее 3). Если элементов меньше, возвращаем 0.
- Сортируем массив для эффективного поиска с помощью двух указателей.
- Инициализируем переменную
closestSum
начальным значением (сумма первых трех элементов). - Вычисляем начальную минимальную разницу
minDiff
междуclosestSum
и целевым значением. - Перебираем первый элемент тройки в цикле:
- Пропускаем дубликаты для оптимизации (если текущий элемент равен предыдущему).
- Устанавливаем два указателя:
left
(сразу после текущего элемента) иright
(конец массива). - Пока
left
<right
:- Вычисляем текущую сумму трех элементов:
nums[i] + nums[left] + nums[right]
. - Вычисляем разницу между текущей суммой и целевым значением.
- Если нашли точное совпадение (разница = 0), сразу возвращаем результат.
- Если текущая разница меньше минимальной, обновляем
minDiff
иclosestSum
. - Двигаем указатели: если текущая сумма меньше целевого значения,
увеличиваем
left
, иначе уменьшаемright
.
- Вычисляем текущую сумму трех элементов:
- Возвращаем найденную ближайшую сумму
closestSum
.
Временная сложность: O(n²), где n - размер массива.
Сортировка массива занимает O(n log n), а алгоритм с двумя указателями требует O(n²) операций в худшем случае.
Пространственная сложность: O(log n) или O(1), в зависимости от реализации сортировки.
Помимо входного массива, мы используем только константное количество дополнительных переменных.
func ThreeSumClosest(nums []int, target int) int {
n := len(nums)
if n < 3 {
return 0 // Недостаточно элементов
}
// Сортируем массив для использования двух указателей
sort.Ints(nums)
// Инициализируем переменную для хранения ближайшей суммы
// с максимально возможным значением разницы
closestSum := nums[0] + nums[1] + nums[2]
minDiff := math.Abs(float64(closestSum - target))
// Перебираем первый элемент тройки
for i := 0; i < n-2; i++ {
// Пропускаем дубликаты для первого элемента
if i > 0 && nums[i] == nums[i-1] {
continue
}
// Используем два указателя для поиска оставшихся двух элементов
left, right := i+1, n-1
for left < right {
currentSum := nums[i] + nums[left] + nums[right]
currentDiff := math.Abs(float64(currentSum - target))
// Если нашли точное совпадение, сразу возвращаем результат
if currentSum == target {
return currentSum
}
// Обновляем ближайшую сумму, если текущая разница меньше
if currentDiff < minDiff {
minDiff = currentDiff
closestSum = currentSum
}
// Двигаем указатели в зависимости от текущей суммы
if currentSum < target {
left++
} else {
right--
}
}
}
return closestSum
}
Даны два массива целых чисел A и B. Необходимо найти минимальное значение абсолютной разницы |A[i] - B[j]| между любыми элементами этих массивов.
Примеры:
Пример 1:
Вход: A = {0}, B = {1}
Выход: 1
Пояснение: Единственная возможная разница |0-1| = 1.
Пример 2:
Вход: A = {10, 0, 2}, B = {5, 100, 12}
Выход: 2
Пояснение: Минимальная абсолютная разница достигается для пары (10, 12): |10-12| = 2.
Алгоритм решения
Для решения этой задачи мы будем использовать алгоритм двух указателей на отсортированных массивах. Основная идея:
- Сортируем оба массива A и B
- Устанавливаем два указателя: i на начало массива A и j на начало массива B
- Пока оба указателя не достигли конца своих массивов:
- Вычисляем абсолютную разницу между текущими элементами |A[i] - B[j]|
- Обновляем минимальную разницу, если нашли меньшую
- Двигаем указатель на массив с меньшим текущим элементом
Временная сложность: O(n log n + m log m), где n и m - размеры массивов A и B соответственно.
- Сортировка массива A: O(n log n)
- Сортировка массива B: O(m log m)
- Проход по массивам с двумя указателями: O(n + m)
Пространственная сложность: O(1) дополнительной памяти (не считая входных массивов).
func FindMinAbsDifference(A, B []int) int {
if len(A) == 0 || len(B) == 0 {
return 0
}
// Сортируем оба массива
sort.Ints(A)
sort.Ints(B)
minDiff := math.MaxInt32
i, j := 0, 0
// Используем два указателя для прохода по массивам
for i < len(A) && j < len(B) {
diff := int(math.Abs(float64(A[i] - B[j])))
minDiff = min(minDiff, diff)
// Двигаем указатель на меньший элемент
if A[i] < B[j] {
i++
} else {
j++
}
}
return minDiff
}
Дан отсортированный по возрастанию массив уникальных целых чисел, который был циклически сдвинут на неизвестное количество позиций. Требуется найти индекс заданного элемента в этом массиве или вернуть -1, если элемент отсутствует.
Алгоритм решения
- Найти точку сдвига в массиве с помощью бинарного поиска.
- Выполнить бинарный поиск на всем массиве, учитывая сдвиг при вычислении среднего элемента.
- Временная сложность: O(log N), где N - количество элементов в массиве.
- Пространственная сложность: O(1), используется константное дополнительное пространство.
func findTarget(nums []int, target int) int {
if len(nums) == 0 {
return -1
}
// Находим точку сдвига
k := findShiftPoint(nums)
// Выполняем бинарный поиск на всем массиве
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
realMid := (mid + k) % len(nums) // Учитываем сдвиг
if nums[realMid] == target {
return realMid
}
if nums[realMid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return -1
}
// Находит точку сдвига в массиве
func findShiftPoint(nums []int) int {
left, right := 0, len(nums)-1
// Если массив не был сдвинут
if nums[left] < nums[right] {
return 0
}
for left <= right {
mid := left + (right-left)/2
// Проверяем, является ли mid точкой сдвига
if mid < len(nums)-1 && nums[mid] > nums[mid+1] {
return mid + 1
}
if nums[mid] >= nums[0] {
left = mid + 1
} else {
right = mid - 1
}
}
return 0
}
Даны два массива целых чисел A и B одинаковой длины N. Для каждого K от 1 до N необходимо подсчитать количество чисел, которые встречаются как в первых K элементах массива A, так и в первых K элементах массива B. Результат нужно вернуть в виде массива длины N+1, где элемент с индексом K содержит количество общих чисел для первых K элементов обоих массивов.
Алгоритм решения
- Инициализировать массив результатов и хеш-таблицу для отслеживания встречаемости чисел.
- Для каждого K от 1 до N:
- Обновить хеш-таблицу для K-го элемента массива A.
- Обновить хеш-таблицу для K-го элемента массива B.
- Подсчитать количество общих элементов для первых K элементов.
- Записать результат в массив результатов.
- Вернуть массив результатов.
- Временная сложность: O(N), где N - длина массивов.
- Пространственная сложность: O(N) для хранения хеш-таблицы и массива результатов.
func countCommonPrefixes(a, b []int) []int {
n := len(a)
result := make([]int, n+1)
has := make(map[int]int)
for k := 1; k <= n; k++ {
// Обновляем хеш-таблицу для текущего элемента a[k-1]
if _, exists := has[a[k-1]]; !exists {
has[a[k-1]] = 0
}
if has[a[k-1]] == 1 {
has[a[k-1]] = 2
} else {
has[a[k-1]] = 0
}
// Обновляем хеш-таблицу для текущего элемента b[k-1]
if _, exists := has[b[k-1]]; !exists {
has[b[k-1]] = 1
}
if has[b[k-1]] == 0 {
has[b[k-1]] = 2
}
// Подсчитываем общие элементы
count := 0
for i := 0; i < k; i++ {
if has[a[i]] == 2 {
count++
}
}
result[k] = count
}
return result
}
Дан массив целых чисел. Необходимо "сжать" его, представив последовательные числа в виде диапазонов. Каждый диапазон должен быть представлен либо одним числом (если диапазон состоит из одного числа), либо парой чисел (начало и конец диапазона). Результат должен быть представлен в виде массива массивов.
Вход: [-3, -10, 1, -2, 2, 3, 5]
Выход: [[-10], [-3, -2], [1, 3], [5]]
Алгоритм решения
- Создать словарь (map) для хранения всех уникальных чисел из входного массива.
- Пока словарь не пуст:
- Выбрать произвольное число из словаря.
- Найти максимальный непрерывный диапазон чисел, содержащих выбранное число.
- Добавить найденный диапазон в результат.
- Удалить все числа найденного диапазона из словаря.
- Вернуть полученный результат.
Временная сложность: O(n), где n - количество элементов в исходном массиве.
Пространственная сложность: O(n) для хранения словаря и результата.
func CompressArray(arr []int) [][]int {
// Создаем словарь для хранения элементов массива
dict := make(map[int]bool)
for _, num := range arr {
dict[num] = true
}
result := [][]int{}
for len(dict) > 0 {
// Выбираем произвольный ключ из словаря
var key int
for k := range dict {
key = k
break
}
// Ищем максимальные l и r
l, r := key, key
for dict[l-1] {
l--
}
for dict[r+1] {
r++
}
// Добавляем результат
if l == r {
result = append(result, []int{l})
} else {
result = append(result, []int{l, r})
}
// Удаляем найденные числа из словаря
for i := l; i <= r; i++ {
delete(dict, i)
}
}
return result
}
Даны два массива целых чисел: a
и b
. Необходимо найти количество
подотрезков массива a
,
которые являются подпоследовательностями массива b
.
Алгоритм решения
- Инициализируем счетчик
count
для подсчета подходящих подотрезков. - Перебираем все возможные левые границы
l
в массивеa
. - Для каждой левой границы:
- Инициализируем счетчик
cnt
для отслеживания совпадений с элементамиb
. - Проходим по элементам
a
, начиная с позицииl
, сравнивая их с элементамиb
. - Если находим совпадение, увеличиваем
cnt
и переходим к следующему элементуb
. - Если
cnt
достигает длиныb
, значит, мы нашли полную подпоследовательность:- Добавляем к
count
количество оставшихся элементов вa
. - Прерываем внутренний цикл и переходим к следующей левой границе.
- Добавляем к
- Инициализируем счетчик
- Возвращаем итоговое значение
count
.
- Временная сложность: O(n * m), где n - длина массива a, m - длина массива b. В худшем случае, для каждого элемента a мы можем пройти по всем элементам b.
- Пространственная сложность: O(1), так как мы используем только несколько дополнительных переменных, независимо от размера входных данных.
func countSubarrays(a, b []int) int {
count := 0
n := len(a)
m := len(b)
for l := 0; l < n; l++ {
cnt := 0
for i, j := l, 0; i < n && j < m; i++ {
if a[i] == b[j] {
j++
cnt++
}
if cnt == m {
count += n - i
break
}
}
}
return count
}
Дан массив, состоящий только из нулей и единиц. Гарантируется, что в массиве есть хотя бы один ноль и одна единица. Необходимо найти позицию нуля, у которого расстояние до ближайшей единицы максимально.
Алгоритм решения
- Инициализируем переменные:
lastOne
: позиция последней встреченной единицы (изначально -1)maxDistance
: максимальное найденное расстояниеresult
: позиция нуля с максимальным расстоянием
- Проходим по массиву слева направо:
- Если встречаем единицу:
- Если это первая единица, обрабатываем случай начальных нулей
- Иначе вычисляем расстояние до предыдущей единицы
- Если новое расстояние больше максимального, обновляем
maxDistance
иresult
- Обновляем позицию последней единицы
- Если встречаем единицу:
- После прохода проверяем случай конечных нулей
- Возвращаем найденную позицию нуля
- Временная сложность: O(n), где n - длина массива. Алгоритм проходит по массиву ровно один раз.
- Пространственная сложность: O(1), так как используется фиксированное количество дополнительных переменных, независимо от размера входных данных.
func findMaxDistanceZero(arr []int) int {
n := len(arr)
lastOne := -1
maxDistance := 0
result := 0
for i := 0; i < n; i++ {
if arr[i] == 1 {
if lastOne == -1 {
// Обработка случая, когда последовательность начинается с нуля
maxDistance = i
result = 0
} else {
// Вычисление расстояния между текущей единицей и предыдущей
distance := (i - lastOne) / 2
if distance > maxDistance {
maxDistance = distance
result = lastOne + distance
}
}
lastOne = i
}
}
// Обработка случая, когда последовательность заканчивается нулем
if n-1-lastOne > maxDistance {
result = n - 1
}
return result
}
Дан массив целых чисел длины n > 1. Необходимо найти минимальное произведение, которое можно получить из двух чисел массива с уникальными позициями.
Алгоритм решения
- Проверяем длину массива. Если она меньше или равна 1, возвращаем 0, так как невозможно выбрать два уникальных числа.
- Инициализируем переменные для хранения двух максимальных (max1, max2) и двух минимальных (min1, min2) чисел.
- Проходим по массиву один раз, обновляя значения max1, max2, min1 и min2.
- После прохода по массиву проверяем три условия:
- Если min1 ≤ 0 и max1 ≥ 0, возвращаем min1 * max1 (случай с отрицательными и неотрицательными числами).
- Если min1 > 0 (все числа положительные), возвращаем min1 * min2.
- В противном случае (все числа отрицательные), возвращаем max1 * max2.
- Временная сложность: O(n), где n - длина входного массива. Мы проходим по массиву только один раз.
- Пространственная сложность: O(1), так как мы используем фиксированное количество дополнительных переменных независимо от размера входного массива.
func minProduct(arr []int) int {
if len(arr) <= 1 {
return 0 // Невозможно выбрать два уникальных числа
}
max1, max2 := math.MinInt32, math.MinInt32
min1, min2 := math.MaxInt32, math.MaxInt32
for _, num := range arr {
// Обновляем максимальные числа
if num > max1 {
max2 = max1
max1 = num
} else if num > max2 {
max2 = num
}
// Обновляем минимальные числа
if num < min1 {
min2 = min1
min1 = num
} else if num < min2 {
min2 = num
}
}
// Проверяем условия и возвращаем результат
if min1 <= 0 && max1 >= 0 {
return min1 * max1
} else if min1 > 0 {
return min1 * min2
} else {
return max1 * max2
}
}
Дано N пар чисел a_i и b_i, представляющих отрезки на числовой прямой от (a_i, 0) до (1, b_i). Требуется найти количество отрезков, которые не пересекаются с другими отрезками в этом множестве.
Алгоритм решения
- Создаем структуру Segment для представления отрезков с полями a и b.
- Формируем слайс отрезков из входных данных a и b.
- Сортируем отрезки по возрастанию координаты a (начальной точки).
- Инициализируем счетчик непересекающихся отрезков (count) и переменную для хранения максимальной конечной точки (maxB).
- Проходим по отсортированному массиву отрезков слева направо:
- Если конечная точка текущего отрезка (b) больше maxB, увеличиваем count и обновляем maxB.
- Возвращаем значение count как результат.
- Временная сложность: O(N log N), где N - количество отрезков. Основной вклад вносит сортировка отрезков.
- Пространственная сложность: O(N) для хранения отсортированного массива отрезков.
type Segment struct {
a, b int
}
func countNonIntersectingSegments(a, b []int) int {
n := len(a)
segments := make([]Segment, n)
for i := 0; i < n; i++ {
segments[i] = Segment{a[i], b[i]}
}
// Сортируем отрезки по координате a
sort.Slice(segments, func(i, j int) bool {
return segments[i].a < segments[j].a
})
count := 0
maxB := 0
for i := 0; i < n; i++ {
if segments[i].b > maxB {
count++
maxB = segments[i].b
}
}
return count
}
Дан массив, состоящий только из 0 и 1. Требуется найти такую позицию для размещения нуля, чтобы минимальное расстояние до ближайшей единицы было максимальным.
Алгоритм решения
- Инициализируем переменные:
lastOne
: индекс последней встреченной единицы (изначально -1)maxDistance
: максимальное найденное расстояниеoptimalPosition
: оптимальная позиция для нуля
- Проходим по массиву слева направо:
- Если встречаем 1:
- Если это первая единица, сравниваем расстояние от начала массива
- Иначе вычисляем расстояние между текущей и предыдущей единицей
- Обновляем
maxDistance
иoptimalPosition
, если найдено большее расстояние - Обновляем
lastOne
- Если встречаем 1:
- После прохода проверяем расстояние от последней единицы до конца массива
- Возвращаем
optimalPosition
- Временная сложность: O(n), где n - длина входного массива. Алгоритм проходит по массиву ровно один раз.
- Пространственная сложность: O(1), так как используется только фиксированное количество дополнительных переменных, независимо от размера входного массива.
func findOptimalZeroPosition(arr []int) int {
n := len(arr)
lastOne := -1
maxDistance := 0
optimalPosition := 0
for i := 0; i < n; i++ {
if arr[i] == 1 {
if lastOne == -1 {
// Первая единица в массиве
if i > maxDistance {
maxDistance = i
optimalPosition = 0
}
} else {
// Расстояние между текущей и предыдущей единицей
distance := (i - lastOne) / 2
if distance > maxDistance {
maxDistance = distance
optimalPosition = (i + lastOne) / 2
}
}
lastOne = i
}
}
// Проверяем расстояние от последней единицы до конца массива
if n - 1 - lastOne > maxDistance {
maxDistance = n - 1 - lastOne
optimalPosition = n - 1
}
return optimalPosition
}