Given the head
of a singly linked list, your task is to return the middle node of the linked list. If the linked list contains an even number of nodes, there will be two middle nodes. In such cases, you should return the second middle node. You must produce a solution that is O(N) time, and O(1) space.
For example:
Input: head
= [1,2,3,4,5]
Output: [3,4,5]
Explanation: The middle node of the list is node 3.
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.
Consider edge cases such as an empty list, or a single element list.
How would you approach this problem, and what are the time and space complexities of your solution?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force method for finding the middle of a linked list is like figuring out which item is in the middle of a train without knowing how many train cars there are. We will walk through the entire train step by step.
Here's how the algorithm would work step-by-step:
class ListNode:
def __init__(self, value=0, next_node=None):
self.value = value
self.next_node = next_node
def find_middle_brute_force(head):
list_length = 0
current_node = head
# Iterate through the list to get the length
while current_node:
list_length += 1
current_node = current_node.next_node
middle_index = list_length // 2
current_node = head
current_index = 0
# Traverse the list until the middle is found
while current_index < middle_index:
current_node = current_node.next_node
current_index += 1
return current_node
We can find the middle of a linked list without knowing its total size beforehand by using two 'pointers' that move at different speeds. This cleverly allows us to reach the middle in a single pass through the list.
Here's how the algorithm would work step-by-step:
def middle_node(head):
slow_pointer = head
fast_pointer = head
# Traverse list with slow_pointer moving 1 step and fast_pointer moving 2 steps
while fast_pointer and fast_pointer.next:
slow_pointer = slow_pointer.next
fast_pointer = fast_pointer.next.next
#When fast_pointer reaches the end, slow_pointer will be at the middle
return slow_pointer
class ListNode:
def __init__(self, value=0, next_node=None):
self.value = value
self.next = next_node
# Example Usage (Not part of the solution, for testing purposes only)
if __name__ == '__main__':
# Create a sample linked list: 1 -> 2 -> 3 -> 4 -> 5
head_node = ListNode(1)
head_node.next = ListNode(2)
head_node.next.next = ListNode(3)
head_node.next.next.next = ListNode(4)
head_node.next.next.next.next = ListNode(5)
middle = middle_node(head_node)
print(middle.value) # Output: 3
# Create another list: 1 -> 2 -> 3 -> 4 -> 5 -> 6
head_node = ListNode(1)
head_node.next = ListNode(2)
head_node.next.next = ListNode(3)
head_node.next.next.next = ListNode(4)
head_node.next.next.next.next = ListNode(5)
head_node.next.next.next.next.next = ListNode(6)
middle = middle_node(head_node)
print(middle.value) # Output: 4
Case | How to Handle |
---|---|
Null or empty linked list (head is null) | Return null immediately as there is no middle node in an empty list. |
Single-node linked list | Return the head node itself, as it's the middle node. |
Two-node linked list | Return the second node, fulfilling the requirement to return the 'second' middle node in case of even length. |
Large linked list (potential performance concerns) | The fast and slow pointer approach has O(n) time complexity and constant space complexity, scaling efficiently. |
Linked list with duplicate values | The values within the nodes are irrelevant to finding the middle node using the fast/slow pointer approach. |
Linked list with a very long length near integer limit for storing length | The fast and slow pointer approach avoids storing the length, preventing overflow issues. |
Linked list with negative, zero, and positive values | Node values do not affect the algorithm, so it works fine with any value. |
Memory limitations prevent creating a large array to store node values. | The fast/slow pointer method uses constant space, avoiding memory limitations. |