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 · 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.

given by LeetCode, don't write this in the solution
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)

find where the second half starts
slow = fast = head
while fast and fast.next:   # fast can still jump 2
    slow = slow.next        # 1 step
    fast = fast.next.next   # 2 steps

Fast 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

reverse the list starting at curr; prev ends as the new head
prev = None
while curr:
    nxt = curr.next     # save
    curr.next = prev    # flip
    prev = curr         # move prev
    curr = nxt          # move curr

The 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

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

Doubt 1: why start 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

  1. Copy every value into vals.
  2. i, j = 0, len(vals) - 1; best = 0.
  3. While i < j: update best with vals[i] + vals[j]; move i right and j left.
  4. Return best.

6Code (Python)

Brute force: array + two pointers
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 best

7Code line by line

linewhat 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) - 1First 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 -= 1Next pair inwards. Both move in the same round.
return bestThe maximum twin sum.

8Dry run

[4] → [2] → [2] → [3] → vals = [4, 2, 2, 3]

roundi, jpairsumbest
10, 34 and 377
21, 22 and 247
32, 1i < j is false → stop, return 7

9Complexity & remember

Remember the brute forceCopy to array → two pointers from both ends → 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

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

Doubt 1: why stop on 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.
Doubt 2: what about the teacher's odd example 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

  1. Fast & slow from head until fast or fast.next is None. slow = start of the second half.
  2. Reverse from slow with prev / curr / nxt. prev = right pointer.
  3. left = head, right = prev, best = 0.
  4. While right: update best with left.val + right.val; move both one step.
  5. Return best.

6Code (Python)

Optimal: fast & slow + reverse second half, O(1) space
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 best

7Code line by line

linewhat 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 = slowReverse only from slow to the end. The node at slow will point to None afterwards.
nxt = curr.next … curr = nxtThe four reversal lines. When done, curr is None and prev is the old last node.
left, right = head, prevLeft = 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.nextLeft 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):

roundleft, rightsumwhat changesbest
1[4] (1st), [3] (4th)7left → 2nd node, right → 3rd node7
2[2] (2nd), [2] (3rd)4left → 3rd node, right → None7
3right is None–loop endsreturn 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

Remember Maximum Twin SumPalindrome recipe with + instead of ==: fast & slow → reverse from slow → best = max(best, left.val + right.val) while right. No early exit.

Part C · Revision page

Brute forceOptimal
how we reach the "right" endarray index j, moving j -= 1reversed second half, moving right = right.next
loopwhile i < jwhile right
timeO(n + n/2)O(n/2 + n/2 + n/2)
spaceO(n)O(1)
Palindrome ListMaximum Twin Sum
steps 1–2identical: fast & slow to slow, reverse from slow
step 3 per pairif left.val != right.val: return Falsebest = max(best, left.val + right.val)
early exit?yes, first mismatchno, must see all pairs
lengthsodd or evenalways even
If you remember only 5 lines 1. Twins = i-th from the front and i-th from the back.
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.
Mistakes to avoid ✗ looping on left instead of right (right runs out first → crash)
✗ 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)
test it yourself (paste under any of the solutions above)
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])))      # 100001

Based on this video: Maximum Twin Sum of a Linked List | Reversal pattern