328. Odd Even Linked List
Problem Description
Given the head of a singly linked list, we are tasked to reorder it in a specific way. We have to rearrange the nodes such that those at the odd positions (1st, 3rd, 5th, etc.) come before those at the even positions (2nd, 4th, 6th, etc.). It's important to maintain the original relative order of the nodes within the odd group and within the even group. For example, if the original linked list is 1 -> 2 -> 3 -> 4 -> 5, after reordering it should look like 1 -> 3 -> 5 -> 2 -> 4. This reordering needs to be achieved with certain efficiency constraints: we can't use extra storage space, meaning we must rearrange the nodes in-place, and we must do it with a time complexity of O(n), where n is the number of nodes in the linked list.
Intuition
To solve this problem efficiently, we exploit the in-place nature of linked list reordering. Instead of creating new lists or arrays which would require extra space, we can carefully rearrange the nodes by modifying their next
pointers.
The approach is to create two pointers, one that will traverse the odd nodes (a
in the solution code) and another for the even nodes (b
). The pointers essentially alternate, where a
progresses to the node pointed to by b.next
and b
then moves to the node pointed to by the updated a.next
. This way, a
is skipping over the even nodes to link together all the odd nodes, and b
does the same among the even nodes. This also maintains the original relative order within each subgroup of nodes.
After the loop, all odd-indexed nodes and even-indexed nodes are grouped separately, but we need to link the last node of the odd nodes to the first of the even nodes. To facilitate this, before starting the reordering process, we keep a separate pointer, c
, at the first even node. When the reordering is complete (which is when b
or b.next
is None
, indicating the end of the list), we simply link the last node of the odd-indexed group (pointed to by a
) to the first of the even-indexed group (pointed to by c
). At this point, our reordering is complete and we can return the head
of the modified list as the final output.
Learn more about Linked List patterns.
Solution Approach
The implementation of the solution follows a specific pattern where the nodes are reordered in-place using only a few pointers, without the need for additional data structures:
-
First, we initialize three pointers:
a
, which starts at the head of the list and will be used to iterate over odd indexed nodes.b
, which starts at the second node (head.next) and will be used to iterate over even indexed nodes.c
, which keeps track of the head of the even indexed sublist (also starts at head.next).
-
We enter a loop that continues until either
b
isNone
(meaning we've reached the end of the list) orb.next
isNone
(meaning there are no more odd nodes to process since odd nodes are followed by even nodes). -
Inside the loop:
- We link
a.next
tob.next
, which is effectively connecting the current odd-indexed node to the next odd-indexed node (a.next
skips an even node). - We update
a
to bea.next
so that it now points to the next odd-indexed node. - We link
b.next
toa.next
, sincea
has advanced one step,a.next
is now an even-indexed node, and we need to skip an odd node. - We update
b
to beb.next
to move to the next even-indexed node.
- We link
-
After the loop, all odd nodes are now connected, and
a
points to the last odd node. We seta.next
toc
, which links the end of the odd-indexed sublist to the head of the even-indexed sublist we tracked earlier. -
Finally, we return
head
, which now points to the reordered list where odd-indexed nodes are grouped at the beginning, followed by even-indexed nodes.
The above steps conform to the given constraints of O(1)
extra space complexity (since we're not using any additional data structures) and O(n)
time complexity (we go through the list at most twice, once for odd nodes and once for even nodes).
The successful execution of this pattern depends heavily on the understanding of linked list structure and careful manipulation of next
pointers, which allows us to make changes in place.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the provided solution approach with a small example to illustrate the method. Suppose we have a linked list:
1 -> 2 -> 3 -> 4
-
We set up our three pointers:
a
starts at 1 (head of the list).b
starts at 2 (second node, head.next).c
also starts at 2 (head of the even sublist).
-
Enter the loop. Currently,
b
is notNone
nor isb.next
None
, so we proceed. -
First iteration:
- Link
a.next
(1’s next) tob.next
(3). Now the list looks like:1 -> 3 -> 4
- Move
a
toa.next
(nowa
is at 3). - Since
a
moved, we now updateb.next
(2’s next) toa.next
which does not change anything sincea.next
is still 4. - Move
b
tob.next
(nowb
is at 4).
- Link
The list now effectively looks like this, with the pointers at their respective positions (a = 3, b = 4, c = 2):
1 -> 3 -> 4 -> 2 -> NULL
- Continue the loop. Now,
b.next
isNone
, we exit the loop. - Connect the last odd node (
a
, now at 3) to the head of the even sublist (c
, which is still at 2):3 -> 2
. The list now becomes:
1 -> 3 -> 2 -> 4 -> NULL
- Finally, return the head of the reordered list, which is still at 1. The fully reordered list, illustrating odd-positioned nodes followed by even-positioned ones, is now:
1 -> 3 -> 2 -> 4
Each step in this approach closely adheres to the specified pattern, ensuring the final output is achieved with an O(n)
time complexity and without using any extra space. The list provided has been reordered in place effectively.
Solution Implementation
1# Definition for singly-linked list.
2class ListNode:
3 def __init__(self, val=0, next=None):
4 self.val = val
5 self.next = next
6
7class Solution:
8 # Function to rearrange nodes of a given linked list such that all
9 # the odd indexed nodes appear before all the even indexed nodes.
10 def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:
11 # If the list is empty, nothing needs to be done.
12 if head is None:
13 return None
14
15 # Initialize two pointers, one for odd indexed nodes (odd_ptr)
16 # and one for even indexed nodes (even_ptr), and also keep track of
17 # the start of even indexed nodes (even_head).
18 odd_ptr = head
19 even_ptr = even_head = head.next
20
21 # Traverse the list, rearranging the nodes.
22 while even_ptr and even_ptr.next:
23 # Make the next of odd_ptr link to the node after the next
24 # (effectively the next odd indexed node).
25 odd_ptr.next = even_ptr.next
26 # Move the odd_ptr to its new next.
27 odd_ptr = odd_ptr.next
28
29 # Connect the next of even_ptr to the node after the next
30 # (effectively the next even indexed node).
31 even_ptr.next = odd_ptr.next
32 # Move the even_ptr to its new next.
33 even_ptr = even_ptr.next
34
35 # After all the odd indexed nodes have been linked, append
36 # the even indexed nodes to the end of the list formed by odd indexed nodes.
37 odd_ptr.next = even_head
38
39 # Return the rearranged list.
40 return head
41
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public ListNode oddEvenList(ListNode head) {
13 // If the list is empty, return null.
14 if (head == null) {
15 return null;
16 }
17
18 // Initialize pointers for manipulation.
19 // 'odd' points to the last node in the odd-indexed list.
20 // 'even' points to the last node in the even-indexed list.
21 // 'evenHead' points to the first node of the even-indexed list.
22 ListNode odd = head;
23 ListNode even = head.next;
24 ListNode evenHead = even;
25
26 // Iterate over the list to rewire nodes.
27 while (even != null && even.next != null) {
28 // Link the next odd node.
29 odd.next = even.next;
30 // Move the 'odd' pointer to the next odd node.
31 odd = odd.next;
32
33 // Link the next even node.
34 even.next = odd.next;
35 // Move the 'even' pointer to the next even node.
36 even = even.next;
37 }
38
39 // After reordering, attach the even-indexed list to the end of the odd-indexed list.
40 odd.next = evenHead;
41
42 // Return the head of the modified list.
43 return head;
44 }
45}
46
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* oddEvenList(ListNode* head) {
14 // If the list is empty, just return an empty list
15 if (!head) {
16 return nullptr;
17 }
18
19 // Use 'oddTail' to keep track of the last node in the odd-indexed nodes
20 ListNode* oddTail = head;
21 // Use 'evenHead' and 'evenTail' to keep track of the even-indexed nodes
22 ListNode *evenHead = head->next, *evenTail = evenHead;
23
24 // Iterate as long as there are even nodes to process
25 while (evenTail && evenTail->next) {
26 // Connect the odd nodes
27 oddTail->next = evenTail->next;
28 oddTail = oddTail->next;
29
30 // Connect the even nodes
31 evenTail->next = oddTail->next;
32 evenTail = evenTail->next;
33 }
34
35 // Attach the even nodes to the end of the odd nodes
36 oddTail->next = evenHead;
37
38 // Return the reorganized list
39 return head;
40 }
41};
42
1// This is the TypeScript type definition for a singly-linked list node.
2type ListNode = {
3 val: number;
4 next: ListNode | null;
5};
6
7/**
8 * This function reorders a singly-linked list by separating it into odd-indexed
9 * nodes and even-indexed nodes and then concatenating the even-indexed nodes
10 * to the end of the odd-indexed nodes.
11 * @param {ListNode | null} head - The head node of the linked list.
12 * @return {ListNode | null} - The head of the modified list with odd and even nodes separated.
13 */
14function oddEvenList(head: ListNode | null): ListNode | null {
15 // Return null if the list is empty.
16 if (!head) {
17 return null;
18 }
19
20 // Initialize pointers to manage the odd and even parts of the list.
21 let odd: ListNode = head; // Points to the last node in the odd list.
22 let even: ListNode | null = head.next; // Points to the first node in the even list.
23 let evenHead: ListNode | null = head.next; // Keeps track of the head of the even list.
24
25 // Iterate over the list to separate nodes into odd and even lists.
26 while (even && even.next) {
27 odd.next = even.next; // Link next odd node.
28 odd = odd.next; // Move odd pointer to the next node.
29 even.next = odd.next; // Link next even node.
30 even = even.next; // Move even pointer to the next node.
31 }
32
33 // Attach the even list to the end of the odd list.
34 odd.next = evenHead;
35
36 // Return the head of the modified list.
37 return head;
38}
39
Time and Space Complexity
The given Python code reorders a linked list so that all the odd nodes are listed before the even nodes, maintaining their relative order. To analyze the time complexity and space complexity of the function oddEvenList
, let's go through it step by step:
Time Complexity:
- The while loop continues until it has traversed all the nodes of the linked list because it stops only when
b
isNone
(meaning the end of the list) orb.next
isNone
(meaning the last node is reached). - Inside the loop, operations have a constant time complexity (
O(1)
), which includes assigningnext
pointers fora
andb
. - Since each node is visited once during the traversal, the total time complexity is based on the number of nodes in the linked list (
n
). Therefore, the time complexity isO(n)
wheren
is the number of nodes in the linked list.
Space Complexity:
- The algorithm only uses a constant amount of extra space for pointers
a
,b
, andc
irrespective of the size of the input linked list. - No additional data structures are used that grow with the input size.
- Therefore, the space complexity is
O(1)
constant space.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!