LeetCode 2130 - Maximum Twin Sum of a Linked List
The problem gives us a singly linked list with an even number of nodes. For every node at index i, there is a corresponding twin node at index n - 1 - i, where n is the total number of nodes in the list. We define the twin sum as: - node[i].val + node[n - 1 - i].
Difficulty: 🟡 Medium
Topics: Linked List, Two Pointers, Stack
Solution
Problem Understanding
The problem gives us a singly linked list with an even number of nodes. For every node at index i, there is a corresponding twin node at index n - 1 - i, where n is the total number of nodes in the list.
We define the twin sum as:
node[i].val + node[n - 1 - i].val
Our task is to compute the maximum twin sum among all valid twin pairs.
For example, in the list:
[5,4,2,1]
The pairs are:
5 + 1 = 64 + 2 = 6
So the answer is 6.
The important detail is that the list is singly linked. Unlike arrays, we cannot directly access the last element or move backward efficiently. This restriction is what makes the problem interesting.
The constraints are large:
- Up to
10^5nodes - Node values up to
10^5
Because of this, any solution worse than linear or near-linear time may be too slow. We also need to think carefully about memory usage.
The problem guarantees:
- The list length is always even
- There are at least 2 nodes
- Every node contains a positive integer
These guarantees simplify the implementation because we never need to handle odd-length lists or empty lists.
Several edge cases are important:
- A list with only two nodes
- All twin sums being equal
- Very large node values
- Large lists where inefficient approaches become too slow
- Cases where the maximum twin sum appears in the middle rather than at the ends
Approaches
Brute Force Approach
The most direct idea is to first copy all linked list values into an array.
Once we have the array, accessing twin nodes becomes easy because arrays support random indexing. For every index i in the first half of the array, we compute:
values[i] + values[n - 1 - i]
We keep track of the maximum sum encountered.
This approach is straightforward and works correctly because the array preserves the original order of the linked list nodes.
However, although this solution is already acceptable for the problem constraints, it uses additional memory proportional to the size of the list.
A truly naive brute force approach without converting to an array would repeatedly scan the linked list to find the matching twin node for every position. That would require nested traversals and would result in quadratic time complexity, which is too slow for 10^5 nodes.
Optimal Approach
The key insight is that we only need to compare nodes from opposite ends of the list.
Since singly linked lists cannot move backward, we can solve this by reversing the second half of the linked list in place.
The high-level idea is:
- Use slow and fast pointers to find the middle of the list.
- Reverse the second half.
- Traverse both halves simultaneously.
- Compute twin sums and track the maximum.
After reversal:
- The first half moves forward normally.
- The reversed second half now also moves forward in the order we need.
This transforms the problem into a simple linear comparison between two linked lists.
Because we reverse the list in place, we achieve constant extra space usage.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force with repeated scans | O(n²) | O(1) | Finds each twin by traversing repeatedly |
| Array-based approach | O(n) | O(n) | Simple and intuitive |
| Optimal | O(n) | O(1) | Reverses second half in place |
Algorithm Walkthrough
Optimal In-Place Reversal Algorithm
- Use two pointers,
slowandfast, starting at the head.
Move:
slowby one stepfastby two steps
When fast reaches the end, slow will point to the start of the second half.
2. Reverse the second half of the linked list.
Use the standard linked list reversal technique:
- Track
prev - Store
next_node - Reverse pointers one by one
After reversal, the second half is now ordered from the end toward the middle. 3. Initialize two pointers:
first_halfat the original headsecond_halfat the head of the reversed second half
- Traverse both halves simultaneously.
At each step:
- Compute the twin sum
- Update the maximum sum
- Move both pointers forward
- Return the maximum twin sum found.
Why it works
The slow and fast pointer technique guarantees that we split the list exactly in half because the list length is always even.
Reversing the second half aligns corresponding twin nodes in traversal order:
Original:
1 -> 2 -> 3 -> 4
Second half reversed:
4 -> 3
Now:
1aligns with42aligns with3
This exactly matches the twin relationship defined in the problem. Since every twin pair is examined once, and we track the maximum, the algorithm always produces the correct answer.
Python Solution
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
from typing import Optional
class Solution:
def pairSum(self, head: Optional[ListNode]) -> int:
slow = head
fast = head
# Find the middle of the list
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse the second half
prev = None
current = slow
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
# Compute maximum twin sum
max_sum = 0
first_half = head
second_half = prev
while second_half:
twin_sum = first_half.val + second_half.val
max_sum = max(max_sum, twin_sum)
first_half = first_half.next
second_half = second_half.next
return max_sum
The implementation begins by locating the midpoint using the classic slow and fast pointer technique. Because the list length is guaranteed to be even, the slow pointer ends exactly at the beginning of the second half.
Next, the second half is reversed in place. The variables prev, current, and next_node are used to safely reverse pointer directions without losing access to the remaining nodes.
After reversal, the list structure allows twin nodes to be traversed simultaneously from left to right. One pointer starts from the original head, while another starts from the reversed second half.
Each iteration computes one twin sum and updates the running maximum.
The algorithm finishes after all twin pairs have been processed.
Go Solution
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func pairSum(head *ListNode) int {
slow := head
fast := head
// Find middle of the list
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
// Reverse second half
var prev *ListNode
current := slow
for current != nil {
nextNode := current.Next
current.Next = prev
prev = current
current = nextNode
}
// Compute maximum twin sum
maxSum := 0
firstHalf := head
secondHalf := prev
for secondHalf != nil {
twinSum := firstHalf.Val + secondHalf.Val
if twinSum > maxSum {
maxSum = twinSum
}
firstHalf = firstHalf.Next
secondHalf = secondHalf.Next
}
return maxSum
}
The Go implementation follows the same algorithmic structure as the Python solution.
One difference is pointer handling. In Go, linked list nodes are manipulated using explicit pointers, so reversing the list involves careful reassignment of Next references.
Go also does not provide a built-in max function for integers, so the maximum value is updated manually using an if statement.
Since node values are at most 10^5, integer overflow is not a concern for Go's standard int type.
Worked Examples
Example 1
Input:
[5,4,2,1]
Step 1: Find the middle
| Slow | Fast |
|---|---|
| 5 | 5 |
| 4 | 2 |
| 2 | nil |
slow now points to 2.
Step 2: Reverse second half
Original second half:
2 -> 1
Reversed:
1 -> 2
Step 3: Compare twin pairs
| First Half Node | Second Half Node | Twin Sum | Max Sum |
|---|---|---|---|
| 5 | 1 | 6 | 6 |
| 4 | 2 | 6 | 6 |
Final answer:
6
Example 2
Input:
[4,2,2,3]
Step 1: Find middle
slow ends at the third node (2).
Step 2: Reverse second half
Original:
2 -> 3
Reversed:
3 -> 2
Step 3: Compare pairs
| First Half Node | Second Half Node | Twin Sum | Max Sum |
|---|---|---|---|
| 4 | 3 | 7 | 7 |
| 2 | 2 | 4 | 7 |
Final answer:
7
Example 3
Input:
[1,100000]
Step 1: Find middle
slow points to 100000.
Step 2: Reverse second half
Single-node reversal remains:
100000
Step 3: Compare pairs
| First Half Node | Second Half Node | Twin Sum | Max Sum |
|---|---|---|---|
| 1 | 100000 | 100001 | 100001 |
Final answer:
100001
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each node is visited a constant number of times |
| Space | O(1) | Reversal is performed in place |
The algorithm performs three linear traversals:
- Finding the middle
- Reversing the second half
- Computing twin sums
Each traversal is proportional to the number of nodes, so the total time complexity remains linear.
No additional data structures proportional to input size are allocated, so the extra space complexity is constant.
Test Cases
# Helper utilities for testing
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build_list(values):
dummy = ListNode()
current = dummy
for value in values:
current.next = ListNode(value)
current = current.next
return dummy.next
solution = Solution()
# Example 1
assert solution.pairSum(build_list([5, 4, 2, 1])) == 6 # all twin sums equal
# Example 2
assert solution.pairSum(build_list([4, 2, 2, 3])) == 7 # maximum at outer pair
# Example 3
assert solution.pairSum(build_list([1, 100000])) == 100001 # minimum size list
# Larger symmetric case
assert solution.pairSum(build_list([1, 2, 3, 4, 5, 6])) == 7 # multiple equal sums
# Maximum appears in middle pair
assert solution.pairSum(build_list([1, 10, 20, 30])) == 40 # inner pair largest
# Repeated values
assert solution.pairSum(build_list([7, 7, 7, 7])) == 14 # identical nodes
# Increasing sequence
assert solution.pairSum(build_list([1, 2, 3, 4, 5, 6, 7, 8])) == 9 # consistent sums
# Large values
assert solution.pairSum(build_list([100000, 1, 1, 100000])) == 200000 # large twin sum
# Two identical nodes
assert solution.pairSum(build_list([42, 42])) == 84 # smallest even list
# Uneven distribution
assert solution.pairSum(build_list([9, 1, 8, 2, 7, 3])) == 12 # varied pair sums
Test Summary
| Test | Why |
|---|---|
[5,4,2,1] |
Verifies basic functionality |
[4,2,2,3] |
Checks differing twin sums |
[1,100000] |
Validates minimum list size |
[1,2,3,4,5,6] |
Tests multiple equal sums |
[1,10,20,30] |
Ensures inner pair can be maximum |
[7,7,7,7] |
Tests repeated values |
[1,2,3,4,5,6,7,8] |
Larger even-length traversal |
[100000,1,1,100000] |
Validates large values |
[42,42] |
Simplest possible valid input |
[9,1,8,2,7,3] |
Mixed pair sums |
Edge Cases
Minimum Size List
The smallest valid input contains exactly two nodes. In this case there is only one twin pair. Algorithms that assume at least four nodes may fail or incorrectly handle midpoint logic.
The implementation handles this naturally because the slow and fast pointer traversal still works correctly, and reversing a single-node second half is valid.
Large Node Values
Node values can be as large as 100000. If an implementation uses an inappropriate numeric type in some languages, overflow could become a concern.
The provided Python and Go implementations safely handle these values because the resulting sums remain well within supported integer ranges.
Maximum Twin Sum Not at the Ends
A common mistake is assuming the first and last nodes always produce the maximum twin sum.
For example:
[1,10,20,30]
Twin sums are:
1 + 30 = 31
10 + 20 = 30
In other cases, the inner pair may be larger. The implementation correctly evaluates every twin pair instead of relying on assumptions about ordering.
Reversal Pointer Bugs
Linked list reversal is error-prone because modifying pointers can accidentally disconnect nodes or create cycles.
The implementation avoids this by always storing next_node before changing current.next. This guarantees the remaining portion of the list is never lost during reversal.