Middle of the Linked List
EasyPrompt
Given the head of a singly linked list, return the middle node of the linked list.
If there are two middle nodes, return the second middle node.
Example 1:
Input: head = [1,2,3,4,5]
Output: [3,4,5]
Explanation: The middle node of the list is node 3.Example 2:
Input: head = [1,2,3,4,5,6]
Output: [4,5,6]
Explanation: Since the list has two middle nodes with values 3 and 4, we return the second one.
Constraints:
- The number of nodes in the list is in the range
[1, 100]. 1 <= Node.val <= 100
Approaches
3 approaches with complexity analysis and trade-offs.
This is the most optimal approach, solving the problem in a single pass with constant space. It uses two pointers, a slow pointer and a fast pointer. The slow pointer moves one step at a time, while the fast pointer moves two steps at a time. When the fast pointer reaches the end of the list, the slow pointer will be at the middle.
Algorithm
-
- Initialize two pointers,
slowandfast, both pointing to theheadof the list.
- Initialize two pointers,
-
- Start a loop that continues as long as
fastis notnullandfast.nextis notnull.
- Start a loop that continues as long as
-
- Inside the loop:
- Move
slowone step forward:slow = slow.next. - Move
fasttwo steps forward:fast = fast.next.next.
-
- When the loop terminates, the
slowpointer will be at the middle node of the linked list.
- When the loop terminates, the
-
- Return the
slowpointer.
- Return the
Walkthrough
This classic algorithm, often called the 'Tortoise and Hare' algorithm, is highly efficient for finding the middle of a linked list. The intuition is that by the time the fast pointer (hare) reaches the end of the race (the end of the list), the slow pointer (tortoise) will have covered exactly half the distance and will be at the midpoint.
We initialize both slow and fast pointers to the head of the list. Then, we enter a loop that continues as long as fast and fast.next are not null. Inside the loop, we advance slow by one node (slow = slow.next) and fast by two nodes (fast = fast.next.next).
Let's trace this for both odd and even length lists:
- Odd length (e.g.,
[1,2,3,4,5]):- Initial:
slowat 1,fastat 1. - Step 1:
slowat 2,fastat 3. - Step 2:
slowat 3,fastat 5. - Now,
fast.nextisnull. The loop terminates.slowis at node3, which is the middle.
- Initial:
- Even length (e.g.,
[1,2,3,4,5,6]):- Initial:
slowat 1,fastat 1. - Step 1:
slowat 2,fastat 3. - Step 2:
slowat 3,fastat 5. - Step 3:
slowat 4,fastbecomesnull(sincefast.nextwas 6,fast.next.nextisnull). - The loop terminates.
slowis at node4, which is the second middle node, as required.
- Initial:
This single-pass approach is both time and space efficient.
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode middleNode(ListNode head) { ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }}Complexity
Time
O(N), where N is the number of nodes. Although it's O(N), it's more efficient than the two-pass approach as it only traverses the list once. The `slow` pointer makes N/2 moves.
Space
O(1), as it only uses two extra pointers, regardless of the list's size.
Trade-offs
Pros
Most efficient solution in terms of both time (single pass) and space (constant).
Elegant and a common pattern for solving linked list problems.
Cons
The logic might be slightly less intuitive for beginners compared to the counting approach.
This approach involves converting the linked list into an array. We traverse the linked list from the beginning to the end and store each node in a dynamic array. Once all nodes are stored, the middle node can be found by accessing the element at the middle index of the array, which is array.size() / 2.
Algorithm
-
- Create an
ArrayListto store theListNodeobjects.
- Create an
-
- Initialize a pointer
currentto theheadof the linked list.
- Initialize a pointer
-
- Traverse the linked list using the
currentpointer. In each step:
- Add the
currentnode to theArrayList. - Move
currentto the next node (current = current.next).
- Traverse the linked list using the
-
- After the traversal is complete, calculate the middle index as
middleIndex = list.size() / 2.
- After the traversal is complete, calculate the middle index as
-
- Return the node at
middleIndexfrom theArrayList.
- Return the node at
Walkthrough
The core idea is to leverage the random-access capability of an array. A linked list does not allow direct access to an element by its index, so we first transform it into a data structure that does.
We initialize an ArrayList of ListNode. Then, we iterate through the linked list with a pointer, starting from the head. In each iteration, we add the current node to our ArrayList and move the pointer to the next node. This continues until we reach the end of the list (i.e., the pointer becomes null).
After the loop, our ArrayList contains all the nodes of the linked list in order. The size of the list, N, is simply list.size(). The problem asks for the middle node, which for a list of size N is the node at index N / 2. We can directly retrieve this node from our ArrayList and return it.
For example, if the list is [1,2,3,4,5], N=5. The middle index is 5/2 = 2. The node at index 2 is 3.
If the list is [1,2,3,4,5,6], N=6. The middle index is 6/2 = 3. The node at index 3 is 4, which is the second middle node as required.
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode middleNode(ListNode head) { java.util.List<ListNode> nodes = new java.util.ArrayList<>(); ListNode current = head; while (current != null) { nodes.add(current); current = current.next; } int middleIndex = nodes.size() / 2; return nodes.get(middleIndex); }}Complexity
Time
O(N), where N is the number of nodes in the linked list. We need to traverse the entire list once to populate the array.
Space
O(N), as we need to store all N nodes in the `ArrayList`.
Trade-offs
Pros
Simple to understand and implement.
Provides random access to any node once the array is built.
Cons
High space complexity, which can be a problem for very large linked lists.
Less efficient than other approaches that use constant space.
This approach avoids using extra space by traversing the list twice. The first pass is to count the total number of nodes. The second pass is to traverse the list again from the head to the middle node and return it.
Algorithm
-
- Initialize a counter
countto 0 and a pointercurrenttohead.
- Initialize a counter
-
- Traverse the list with
currentto its end, incrementingcountfor each node.
- Traverse the list with
-
- After the first pass,
countwill hold the total number of nodes.
- After the first pass,
-
- Calculate the number of steps to reach the middle node:
middleSteps = count / 2.
- Calculate the number of steps to reach the middle node:
-
- Reset the
currentpointer back tohead.
- Reset the
-
- Traverse the list again from the
headformiddleStepstimes.
- Traverse the list again from the
-
- Return the
currentnode.
- Return the
Walkthrough
This method is a space-optimized improvement over the array-based approach. Instead of storing all nodes, we first determine the length of the list and then find the middle node based on that length.
The algorithm proceeds in two main steps:
- First Pass (Count Nodes): We initialize a counter to zero and a pointer to the
head. We traverse the entire list, incrementing the counter for each node until we reach the end. Let the final count beN. - Second Pass (Find Middle Node): We calculate the position of the middle node, which is
N / 2. We reset our pointer back to thehead. Then, we traverse the list again, this time forN / 2steps. The node at which the pointer stops is the middle node.
For a list [1,2,3,4,5], N=5. The middle position is 5/2 = 2. We traverse 2 steps from the head: 1 -> 2 -> 3. The result is node 3.
For a list [1,2,3,4,5,6], N=6. The middle position is 6/2 = 3. We traverse 3 steps from the head: 1 -> 2 -> 3 -> 4. The result is node 4.
This approach correctly identifies the second middle node in case of an even-length list.
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */class Solution { public ListNode middleNode(ListNode head) { // First pass: count the number of nodes int count = 0; ListNode current = head; while (current != null) { count++; current = current.next; } // Calculate the middle index int middleIndex = count / 2; // Second pass: traverse to the middle node current = head; for (int i = 0; i < middleIndex; i++) { current = current.next; } return current; }}Complexity
Time
O(N), where N is the number of nodes. The list is traversed 1.5 times in the worst case (once to count, and N/2 times to find the middle), which simplifies to O(N).
Space
O(1), as we only use a few variables (`count`, `current`, `middleSteps`) regardless of the list size.
Trade-offs
Pros
Space efficient, using only constant extra space.
Conceptually straightforward.
Cons
Requires two passes over the linked list, which is less efficient than a single-pass solution.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode middleNode ( ListNode head ) { ListNode slow = head , fast = head ; while ( fast != null && fast . next != null ) { slow = slow . next ; fast = fast . next . next ; } return slow ; } }/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode * middleNode ( ListNode * head ) { ListNode * slow = head , * fast = head ; while ( fast && fast -> next ) { slow = slow -> next ; fast = fast -> next -> next ; } return slow ; } };# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution : def middleNode ( self , head : ListNode ) -> ListNode : slow = fast = head while fast and fast . next : slow , fast = slow . next , fast . next . next return slowVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.