DSA sheet · Linked List · Reversal pattern
Maximum Twin Sum of a Linked List
The teacher puts this right after Palindrome Linked List on purpose: it's the same recipe, with "compare the two values" replaced by "add the two values and keep the biggest sum". We again go array → brute force with an extra list → optimal with fast & slow pointers plus reversing the second half. If you did the palindrome question, try this one on your own first.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Linked lists, fast & slow pointers and reversal from scratch
- Part A · Brute force: copy into an array, two pointers
- Part B · Optimal: middle → reverse second half → add pairs
- Part C · Revision page
Part 0 · What you must know before starting
Nodes, head, None and walking
A linked list is a chain of nodes. Each node holds a value val and a link next to the node after it. The last node points to None. We get only the head (first node), and we visit the others with a walker: curr = head, then curr = curr.next until curr is None.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next[5] → [4] → [2] → [1] → None head
There's no index access. To reach the i-th node you follow i arrows (O(i)). And arrows go forward only: from a node you can't step back to the one before it.
Before changing any node's next, save it (nxt = curr.next). It's the only road to the rest of the list. Overwrite it unsaved and the rest is lost.
Tool 1: fast & slow pointers (the middle in one pass)
slow = fast = head
while fast and fast.next: # fast can still jump 2
slow = slow.next # 1 step
fast = fast.next.next # 2 stepsFast covers double the distance, so when fast hits the end, slow is halfway. For an even length like 4, fast ends on None and slow stands on the 3rd node, the first node of the second half. For an odd length, slow stands on the exact middle node.
The teacher also mentions the slower way: count the length in one pass, then walk n/2 steps in a second pass. It works but takes two passes; fast & slow does it in one.
Tool 2: reversal with prev / curr / nxt
prev = None
while curr:
nxt = curr.next # save
curr.next = prev # flip
prev = curr # move prev
curr = nxt # move currThe first node reversed ends up pointing to None. That's what lets the right-hand walker know when to stop.
What is a "twin"?
In a list of n nodes (n even), node i and node n − 1 − i (counting from 0) are twins: the 1st and last, the 2nd and second-last, and so on. A twin sum is the sum of the two values.
Part A · Brute force: copy into an array, two pointers
LeetCode 2130
1The question in simple words
Given the head of a list with an even number of nodes, find the twin sums and return the largest one.
[5] → [4] → [2] → [1] → None twins: 5+1 = 6, 4+2 = 6 → 6 [4] → [2] → [2] → [3] → None twins: 4+3 = 7, 2+2 = 4 → 7 [1] → [100000] → None twins: 1+100000 = 100001 → 100001
The teacher explains with the list 5 → 2 → 1 → 3 → 1: 5+1 = 6, 2+3 = 5, and the middle 1 has no partner. The biggest is 6. (That list has odd length, which LeetCode never gives; see Doubt 2 in Part B.)
2What the constraints tell us
- Number of nodes: even, from 2 to 10⁵ → never empty, and every node has exactly one twin.
- 10⁵ × 10⁵ = 10¹⁰ → an O(n²) idea will TLE. Her rule of thumb: past about 10⁸–10⁹ simple steps, expect TLE. So we need linear time.
- Values 1 to 10⁵ → a sum is at most 2 × 10⁵, small.
- Her usual reminder: linked-list solutions are almost always linear. Brute force = extra space or extra passes; optimal = no extra space.
3Intuition: on an array it's just two pointers
If the values were in an array, put left at the start and right at the end. Add the two, update a best variable, move left forward and right backward, repeat.
[5, 2, 1, 3, 1]
L R 5 + 1 = 6 → best = 6
L R 2 + 3 = 5 → 6 is still bigger, best stays 6
LR they meet → stop. Answer 6
On a linked list, right -= 1 is impossible: every node knows only the node to its right. The real fix (reverse half the list) is Part B. If you don't know that yet, the simple way out is: copy the values into an array and run the same two pointers.
4Building the logic
- Pass 1: walk the list, append each value to
vals→[5, 2, 1, 3, 1]. i = 0,j = len(vals) − 1,best = 0.- While
i < j:best = max(best, vals[i] + vals[j]), theni += 1,j -= 1. - Why
i < j? With even length the pointers cross after the last twin pair. (With odd length they'd meet at the middle, which has no twin, so we stop there too.) - Unlike the palindrome check, there's no early exit. A later pair might have a bigger sum, so we must look at every pair.
best at 0?→ All values are at least 1, so every twin sum is at least 2 > 0. The first pair will always replace it. (With negative values you'd start at
float('-inf').)5Approach steps
- Copy every value into
vals. i, j = 0, len(vals) - 1;best = 0.- While
i < j: update best withvals[i] + vals[j]; move i right and j left. - Return
best.
6Code (Python)
class Solution:
def pairSum(self, head):
vals = []
curr = head
while curr: # copy the values out
vals.append(curr.val)
curr = curr.next
i, j = 0, len(vals) - 1
best = 0
while i < j: # each twin pair once
best = max(best, vals[i] + vals[j])
i += 1
j -= 1
return best7Code line by line
| line | what it means |
|---|---|
| vals = [] | Extra array, O(n) space. |
| while curr: … | One pass copying values. Now we can read from both ends. |
| i, j = 0, len(vals) - 1 | First and last index: the first twin pair. |
| while i < j: | Stop once the pointers meet or cross. Every pair has been seen. |
| best = max(best, vals[i] + vals[j]) | Keep the bigger of "best so far" and "this pair's sum". |
| i += 1 j -= 1 | Next pair inwards. Both move in the same round. |
| return best | The maximum twin sum. |
8Dry run
[4] → [2] → [2] → [3] → vals = [4, 2, 2, 3]
| round | i, j | pair | sum | best |
|---|---|---|---|---|
| 1 | 0, 3 | 4 and 3 | 7 | 7 |
| 2 | 1, 2 | 2 and 2 | 4 | 7 |
| 3 | 2, 1 | i < j is false → stop, return 7 | ||
9Complexity & remember
- Time O(n + n/2) = O(3n/2): n to copy, n/2 rounds of the two pointers (both move each round, so they meet in the middle after n/2 rounds).
- Space O(n): the array.
best = max(best, a[i] + a[j]) while i < j. No early exit.Part B · Optimal: middle → reverse second half → add pairs
1The question (same), new goal
Same answer, but with O(1) extra space.
2What the constraints tell us
- At least 2 nodes → head and head.next exist, so the fast & slow loop runs at least once. No empty-list check needed.
- Even length → after fast & slow, the two halves have exactly n/2 twin pairs between them.
3Intuition: same as palindrome, add instead of compare
The teacher runs through the patterns again: basic operations (insert, delete, update) don't help. Reversal does: if the second half's arrows pointed backwards, a walker starting at the last node could move towards the middle with plain .next. That's our "right pointer". To know where the second half starts, use fast & slow (one pass instead of "count the length, then walk again").
before: [4] → [2] → [2] → [3] → None
after: [4] → [2] → [2] ← [3]
left right ← both now move with .next
4Building the logic step by step
Step 1: fast & slow to the second half
start [4] → [2] → [2] → [3] → None slow/fast move 1 [4] → [2] → [2] → [3] → None slow fast move 2 [4] → [2] → [2] → [3] → None slow fast stop: fast is None → slow = [2] (position 2), the start of the second half
Step 2: reverse from slow to the end
The piece starting at slow is [2] → [3]. Keep head untouched (it's our left pointer later).
start: prev = None, curr = slow None [2] → [3] → None prev curr round 1: save nxt, flip curr.next (red), move prev and curr None ← [2] [3] → None prev curr nxt prev curr ← after moving round 2: save nxt, flip curr.next (red), move prev and curr None ← [2] ← [3] None prev curr nxt prev curr ← after moving
Now prev is on [3], the old last node. That's our right pointer. The whole picture:
[4] → [2] → [2] ← [3]
head ↓ prev
None
left's walk: 4, 2, 2 right's walk: 3, 2
Step 3: walk both, add, keep the max, stop when right is None
- Loop while right is not None. The second half ends in None because its first reversed node points to None.
- Each round:
best = max(best, left.val + right.val), thenleft = left.next(moving right) andright = right.next(moving left).
right and not on left?→ In the even case, right's piece has n/2 nodes, but left's walk has one more: the first half's last node (the first [2]) still points to the middle node, which now ends the reversed piece. So left's walk is longer (4, 2, 2 against 3, 2). Right reaches None first, exactly when every twin pair is done. Looping on left would read
right.val on None and crash.5 → 2 → 1 → 3 → 1?→ She uses it to explain the idea and says the left half is longer there, so right still hits None first. One detail: our code reverses from slow, and for odd length slow is the middle node. So the middle ends up at the end of both walks (left: 5, 2, 1; right: 1, 3, 1), and the last round adds the middle to itself (1 + 1 = 2). Here that's harmless (max is still 6), but on
1 → 10 → 1 it would wrongly give 20. It doesn't matter for LeetCode because n is always even, but don't reuse this code for odd lengths without stopping one round earlier. (This check is mine; the teacher's drawing skips the middle.)5Approach steps
- Fast & slow from head until
fastorfast.nextis None. slow = start of the second half. - Reverse from slow with prev / curr / nxt.
prev= right pointer. left = head,right = prev,best = 0.- While right: update best with
left.val + right.val; move both one step. - Return best.
6Code (Python)
class Solution:
def pairSum(self, head):
# 1. reach the start of the second half
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 2. reverse the second half
prev = None
curr = slow
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
# 3. twin sums: head walks forward, prev walks back from the end
best = 0
left, right = head, prev
while right:
best = max(best, left.val + right.val)
left = left.next
right = right.next
return best7Code line by line
| line | what it means |
|---|---|
| slow = fast = head while fast and fast.next: … | Slow moves 1, fast moves 2. For even n, fast ends on None and slow on node n/2 (0-based), the first node of the second half. |
| prev = None curr = slow | Reverse only from slow to the end. The node at slow will point to None afterwards. |
| nxt = curr.next … curr = nxt | The four reversal lines. When done, curr is None and prev is the old last node. |
| left, right = head, prev | Left = first node, right = last node: the first twin pair. |
| while right: | Right's walk is the shorter one, so it decides when all pairs are done. |
| best = max(best, left.val + right.val) | Twin sum of this pair, keep the maximum. |
| left = left.next right = right.next | Left moves towards the middle from the front, right moves towards the middle from the back. |
8Dry run
[4] → [2] → [2] → [3], after steps 1 and 2 drawn above (left = 1st node, right = 4th node):
| round | left, right | sum | what changes | best |
|---|---|---|---|---|
| 1 | [4] (1st), [3] (4th) | 7 | left → 2nd node, right → 3rd node | 7 |
| 2 | [2] (2nd), [2] (3rd) | 4 | left → 3rd node, right → None | 7 |
| 3 | right is None | – | loop ends | return 7 |
Another quick check, 5 → 4 → 2 → 1: slow stops on [2]; reversed half is 1 → 2. Pairs: 5+1 = 6, 4+2 = 6 → 6 ✓. And 1 → 100000: slow stops on [100000]; one pair 1 + 100000 = 100001 ✓.
9Complexity & remember
- Middle: O(n/2) (fast jumps two). Reverse half: O(n/2). Twin sums: O(n/2). Total O(3n/2) = O(n).
- Space O(1): only pointers.
- The teacher's point: the time is the same 3n/2 as the brute force. The optimisation is purely in space, O(n) → O(1).
+ instead of ==: fast & slow → reverse from slow → best = max(best, left.val + right.val) while right. No early exit.Part C · Revision page
| Brute force | Optimal | |
|---|---|---|
| how we reach the "right" end | array index j, moving j -= 1 | reversed second half, moving right = right.next |
| loop | while i < j | while right |
| time | O(n + n/2) | O(n/2 + n/2 + n/2) |
| space | O(n) | O(1) |
| Palindrome List | Maximum Twin Sum | |
|---|---|---|
| steps 1–2 | identical: fast & slow to slow, reverse from slow | |
| step 3 per pair | if left.val != right.val: return False | best = max(best, left.val + right.val) |
| early exit? | yes, first mismatch | no, must see all pairs |
| lengths | odd or even | always even |
2. A list can't walk back, so reverse the second half.
3. Find it with slow/fast,
while fast and fast.next.4. Walk head and prev together, adding, while prev isn't None.
5. Same 3n/2 time as the array way; the gain is O(1) space.
✗ returning early like in palindrome (a later pair may be bigger)
✗ moving
head during fast & slow (you lose the left pointer)✗ reusing this code on odd lengths (the middle gets added to itself)
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
s = Solution()
print(s.pairSum(build([5, 4, 2, 1]))) # 6
print(s.pairSum(build([4, 2, 2, 3]))) # 7
print(s.pairSum(build([1, 100000]))) # 100001Based on this video: Maximum Twin Sum of a Linked List | Reversal pattern