Максимальная сумма парных элементов связного списка
Описание задачи #
Реализуйте функцию, которая будет искать максимальную сумму парных элементов связного списка.
Парный элемент для i
'го считается элемент с индексом n - i - 1
, где 0 <= i <= n / 2 - 1
.
То есть, для первого элемента списка парным будет считаться последний элемент списка, для второго - предпоследний и так далее.
Ограничения #
- Список всегда имеет четное количество элементов
- Количество элементов лежит в диапазоне от 2 до 100 000
- Значение элемента лежит в диапазоне от 1 до 100 000
Примеры #
-
Элементы списка:
[5,4,2,1]
Ответ:
6
Пояснение: В списке есть 2 пары элементов:
[5, 1]
и[4, 2]
. Сумма обоих пар равна 6. -
Входные данные:
[4,2,2,3]
Ответ:
7
Пояснение: В списке есть 2 пары элементов:
[4, 3]
и[2, 2]
. Максимальная сумма равна 7. -
Входные данные:
[1,100000]
Ответ:
100001
Решение через преобразование в массив #
Основная сложность этой задачи заключается в использовании связного списка в качестве структуры данных: мы не знаем длину списка и не можем быстро обращаться к элементам по индексу, из-за чего не получается быстро находить парный элемент для текущего узла.
Первая идея, которая приходит в голову — отказаться от использования связного списка в пользу массива. Это сделать несложно: достаточно один раз пройти по всему списку и перенести все элементы в массив. Теперь, имея на руках слайс, мы можем легко находить парные элементы, и задача сводится к простому поиску максимума среди сумм парных элементов.
Реализация #
-
package maximum_twin_sum_of_a_linked_list func pairSumSlice(head *ListNode) int { list := []int{head.Val} for cur := head.Next; cur != nil; cur = cur.Next { list = append(list, cur.Val) } res := 0 for i := 0; i < len(list)/2; i++ { j := len(list) - i - 1 res = max(res, list[i]+list[j]) } return res }
-
import { ListNode } from './ListNode'; export function pairSumSlice(head: ListNode): number { let list: number[] = [head.val]; // Проходим по списку и заполняем массив значениями for (let cur = head.next; cur !== null; cur = cur.next) { list.push(cur.val); } let res = 0; // Находим максимальную пару значений for (let i = 0; i < Math.floor(list.length / 2); i++) { let j = list.length - i - 1; res = Math.max(res, list[i] + list[j]); } return res; }
Оценка сложности #
По времени
Для решения нам нужно дважды проитерироваться по всем элементам: первый раз для создания массива, второй для поиска всех сумм.
Итоговая сложность будет равна O(n)
.
По памяти
O(n)
— так как выделяется память для хранения списка в виде слайса.
Решение через стек #
Решение можно улучшить, сэкономив память: нам необязательно превращать весь список в массив. Достаточно переложить первую половину списка в стек, а затем пройти по второй половине, доставая для каждого следующего элемента его пару из стека.
Однако возникает другая проблема: как я уже упоминал, основное ограничение — это незнание длины списка, а значит, мы не можем сразу определить, где находится его середина. Это ограничение можно обойти с помощью небольшой хитрости, используя два указателя:
currentListElement
— будем перемещать по списку последовательно.oddElementList
— будем двигать через два элемента за раз. В связном списке это делается следующим образом:
oddElementList = oddElementList.Next.Next
Таким образом, когда указатель oddElementList
достигнет конца списка, указатель currentListElement
будет указывать на начало второй половины нашего списка.
После этого останется только пройтись до конца списка указателем currentListElement
, доставая для каждого следующего элемента его пару из стека и находя максимальную сумму.
Реализация #
-
package maximum_twin_sum_of_a_linked_list func pairSumStack(head *ListNode) int { stack := make([]int, 0) currentListElement, oddElementList := head, head for oddElementList != nil && oddElementList.Next != nil { stack = append(stack, currentListElement.Val) currentListElement = currentListElement.Next oddElementList = oddElementList.Next.Next } res := 0 for ; currentListElement != nil; currentListElement = currentListElement.Next { res = max(res, currentListElement.Val+stack[len(stack)-1]) stack = stack[:len(stack)-1] } return res }
-
import { ListNode } from './ListNode'; export function pairSumStack(head: ListNode): number { let stack: number[] = []; let currentListElement: ListNode | null = head; let oddElementList: ListNode | null = head; // Заполняем стек первой половиной списка while (oddElementList !== null && oddElementList.next !== null) { stack.push(currentListElement!.val); currentListElement = currentListElement!.next; oddElementList = oddElementList.next.next; } let res = 0; // Сравниваем элементы с конца первой половины и из второй половины while (currentListElement !== null) { res = Math.max(res, currentListElement.val + stack.pop()!); currentListElement = currentListElement.next; } return res; }
Оценка сложности #
По времени
Для решения нам нужно один раз проитерироваться по всем элементам.
Итоговая сложность будет равна O(n)
.
По памяти
O(n)
— так как выделяется память для хранения элементов в стеке.
Решение #
Но, вероятнее всего, в задаче не подразумевалось использование других структур данных (хотя, в явном виде такого ограничения нет). Поэтому мы можем попробовать решить задачу только с использованием связных списков.
Чтобы быстро находить пары, можно создать вспомогательный список, развернутый в обратном порядке. Для изменения порядка списка можно написать простую функцию reverseList
(изменение происходит in-place без создания нового списка).
Как и в случае со стеком, можно развернуть не весь список, а только одну его половину. Итоговое решение будет очень похоже на решение со стеком, только вместо стека мы будем использовать первую половину списка, развернутую в обратном порядке.
Данное решение будет более оптимальным по памяти, так как изменение порядка происходит in-place и не требует дополнительного пространства под массив или стек.
Реализация #
-
package maximum_twin_sum_of_a_linked_list type ListNode struct { Val int Next *ListNode } func pairSum(head *ListNode) int { firstHalfList, oddElementList := head, head for oddElementList != nil && oddElementList.Next != nil { firstHalfList = firstHalfList.Next oddElementList = oddElementList.Next.Next } revers := reverseList(firstHalfList) maxSum := 0 for revers != nil { maxSum = max(maxSum, head.Val+revers.Val) head, revers = head.Next, revers.Next } return maxSum } func reverseList(head *ListNode) *ListNode { var newHead *ListNode = nil for head != nil { next := head.Next head.Next, newHead = newHead, head head = next } return newHead }
-
import { ListNode } from './ListNode'; export function pairSum(head: ListNode): number { let firstHalfList: ListNode | null = head; let oddElementList: ListNode | null = head; // Ищем середину списка while (oddElementList !== null && oddElementList.next !== null) { firstHalfList = firstHalfList!.next; oddElementList = oddElementList.next.next; } // Разворачиваем вторую половину списка let revers: ListNode | null = reverseList(firstHalfList); let maxSum = 0; // Сравниваем элементы while (revers !== null) { maxSum = Math.max(maxSum, head.val + revers.val); head = head.next!; revers = revers.next; } return maxSum; } function reverseList(head: ListNode | null): ListNode | null { let newHead: ListNode | null = null; while (head !== null) { let next: ListNode | null = head.next; head.next = newHead; newHead = head; head = next; } return newHead; }
Оценка сложности #
По времени
Для решения задачи нам достаточно проитерироваться по исходному списку.
Итоговая сложность будет равна O(n)
.
По памяти
В решении с разворотом половины списка дополнительная память равна O(1)
.