Skip to content

Commit 0557c13

Browse files
committed
add python solutions for linked list in-place reversal pattern
1 parent 6ee7556 commit 0557c13

File tree

3 files changed

+418
-0
lines changed

3 files changed

+418
-0
lines changed

python/reverse-linked-list-2.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
2+
# Reverse Linked List II
3+
Given the head of a singly linked list and two integers left and right where 1 <= left <= right <= n, reverse the nodes of the list from position left to position right, and return the reversed list.
4+
5+
### Constraints:
6+
- The number of nodes in the list is n.
7+
- 1 <= n <= 500.
8+
- -500 <= Node.val <= 500.
9+
- 1 <= left <= right <= n.
10+
11+
### Examples
12+
```javascript
13+
Input: head = [1,2,3,4,5], left = 2, right = 4
14+
Output: [1,4,3,2,5]
15+
16+
Input: head = [5], left = 1, right = 1
17+
Output: [5]
18+
```
19+
20+
### Follow-up:
21+
A linked list can be reversed either iteratively or recursively. Could you implement both?
22+
23+
## Approaches to Solve the Problem
24+
### Approach 1: Iterative Reversal with Dummy Node (Optimal Solution)
25+
##### Intuition:
26+
The problem requires reversing a sublist within a linked list while keeping the rest of the list intact. To achieve this:
27+
28+
1. Traverse the list until you reach the node just before the left position.
29+
2. Reverse the sublist between left and right.
30+
Reconnect the reversed sublist to the rest of the list.
31+
3. A dummy node can help simplify edge cases, such as when the sublist to reverse starts at the head of the list.
32+
33+
Steps:
34+
1. Dummy Node: Create a dummy node before the head to handle edge cases like when left is at the head.
35+
2. Traverse the List: Move a pointer to the node just before left to identify where to start reversing.
36+
3. Reverse the Sublist: Reverse the nodes between positions left and right.
37+
4. Reconnection: Reconnect the reversed sublist back to the rest of the list.
38+
##### Visualization:
39+
For head = [1, 2, 3, 4, 5], left = 2, and right = 4:
40+
```rust
41+
Initial list:
42+
1 -> 2 -> 3 -> 4 -> 5
43+
44+
After reversing the sublist between 2 and 4:
45+
1 -> 4 -> 3 -> 2 -> 5
46+
```
47+
##### Time Complexity:
48+
O(n), where n is the number of nodes in the list. We traverse the list once to reverse the sublist.
49+
##### Space Complexity:
50+
O(1), as we only use a few pointers to modify the list in place.
51+
##### Python Code:
52+
```python
53+
class ListNode:
54+
def __init__(self, val=0, next=None):
55+
self.val = val
56+
self.next = next
57+
58+
def reverseBetween(head: ListNode, left: int, right: int) -> ListNode:
59+
# Step 1: Create a dummy node before the head
60+
dummy = ListNode(0)
61+
dummy.next = head
62+
prev = dummy
63+
64+
# Step 2: Move prev to the node just before left
65+
for _ in range(left - 1):
66+
prev = prev.next
67+
68+
# Step 3: Start the reversal process
69+
curr = prev.next
70+
next_node = None
71+
72+
for _ in range(right - left):
73+
next_node = curr.next
74+
curr.next = next_node.next
75+
next_node.next = prev.next
76+
prev.next = next_node
77+
78+
return dummy.next
79+
```
80+
##### Breakdown of the Code:
81+
1. Dummy Node Creation: A dummy node simplifies the edge case where the reversal starts from the head.
82+
2. Traversing to Left: The pointer prev is moved to the node just before left. This is where the reversal begins.
83+
3. Reversing the Sublist: Using the next pointer of each node, the sublist is reversed in place.
84+
4. Reconnection: Once the sublist is reversed, the nodes are reconnected to the rest of the list.
85+
5. Return: The modified list is returned by pointing to dummy.next.
86+
87+
### Approach 2: Recursive Solution
88+
##### Intuition:
89+
A recursive approach could be employed by focusing on reversing a portion of the list recursively. While this approach is less efficient than the iterative method in terms of space, it's conceptually simpler.
90+
91+
However, recursive approaches for in-place linked list reversal are rarely used in practice for reversing specific sublists due to complexity in handling edge cases and performance concerns related to recursion depth.
92+
93+
Steps:
94+
1. Identify the base case when the left and right positions are met.
95+
2. Reverse the sublist recursively by adjusting the pointers during the unwinding phase.
96+
3. Return the modified head after the recursion completes.
97+
##### Time Complexity:
98+
O(n), where n is the number of nodes in the list.
99+
##### Space Complexity:
100+
O(n), due to the recursion stack.
101+
##### Python Code:
102+
```python
103+
class ListNode:
104+
def __init__(self, val=0, next=None):
105+
self.val = val
106+
self.next = next
107+
108+
def reverseBetween(head: ListNode, left: int, right: int) -> ListNode:
109+
if left == right:
110+
return head
111+
112+
def reverseN(head, n):
113+
if n == 1:
114+
return head, head.next
115+
new_head, successor = reverseN(head.next, n - 1)
116+
head.next.next = head
117+
head.next = successor
118+
return new_head, successor
119+
120+
if left == 1:
121+
new_head, _ = reverseN(head, right)
122+
return new_head
123+
124+
head.next = reverseBetween(head.next, left - 1, right - 1)
125+
return head
126+
```
127+
## Summary
128+
129+
| Approach | Time Complexity | Space Complexity |
130+
|-----------------------------------|-----------------|------------------|
131+
| Iterative | O(n) | O(1) |
132+
| Recursive | O(n) | O(n) |
133+
134+
The iterative solution with dummy node is the most efficient and practical approach due to its O(1) space complexity and straightforward handling of edge cases. The recursive solution is elegant but less efficient because of the recursion depth and overhead.

python/reverse-linked-list.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
2+
# Reverse Linked List
3+
Given the head of a singly linked list, reverse the list and return the reversed list.
4+
5+
### Constraints:
6+
- The number of nodes in the list is the range [0, 5000].
7+
- -5000 <= Node.val <= 5000
8+
9+
### Examples
10+
```javascript
11+
Input: head = [1,2,3,4,5]
12+
Output: [5,4,3,2,1]
13+
14+
Input: head = [1,2]
15+
Output: [2,1]
16+
17+
Input: head = []
18+
Output: []
19+
```
20+
21+
### Follow-up:
22+
A linked list can be reversed either iteratively or recursively. Could you implement both?
23+
24+
## Approaches to Solve the Problem
25+
### Approach 1: Iterative Solution (Optimal)
26+
##### Intuition:
27+
In the iterative approach, the goal is to reverse the direction of the links between the nodes. We can achieve this by maintaining three pointers: prev, curr, and next. We iterate through the list, reversing the next pointer of each node until we reach the end of the list.
28+
29+
- prev: Keeps track of the previous node (initially None because the new tail will point to None).
30+
- curr: Points to the current node being processed.
31+
- next: Points to the next node in the original list so that we can continue processing after modifying the current node’s next pointer.
32+
33+
Steps:
34+
1. Initialize three pointers: prev = None, curr = head, and next = None.
35+
2. While curr is not None, repeat the following steps:
36+
- Store curr.next in next to keep track of the remaining list.
37+
- Set curr.next to prev to reverse the link.
38+
- Move prev to curr and curr to next.
39+
3. After the loop, prev will point to the new head of the reversed list.
40+
4. Return prev.
41+
##### Visualization:
42+
For head = [1, 2, 3, 4, 5]:
43+
```rust
44+
Initial:
45+
prev = None, curr = 1, next = None
46+
47+
Iteration 1:
48+
next = 2
49+
Reverse 1's next to Noneprev = 1, curr = 2
50+
51+
Iteration 2:
52+
next = 3
53+
Reverse 2's next to 1prev = 2, curr = 3
54+
55+
...
56+
57+
Final:
58+
prev = 5 (new head), curr = None
59+
Reversed list: [5, 4, 3, 2, 1]
60+
```
61+
##### Time Complexity:
62+
O(n), where n is the number of nodes in the list. We traverse the list once.
63+
##### Space Complexity:
64+
O(1), as we only use a few pointers to reverse the list in place.
65+
##### Python Code:
66+
```python
67+
class ListNode:
68+
def __init__(self, val=0, next=None):
69+
self.val = val
70+
self.next = next
71+
72+
def reverseList(head: ListNode) -> ListNode:
73+
prev = None
74+
curr = head
75+
76+
while curr:
77+
next_node = curr.next # Store the next node
78+
curr.next = prev # Reverse the current node's pointer
79+
prev = curr # Move prev to current node
80+
curr = next_node # Move to the next node
81+
82+
return prev # prev will be the new head after the loop
83+
```
84+
### Approach 2: Recursive Solution
85+
##### Intuition:
86+
The recursive approach works by breaking the problem into smaller subproblems. The idea is to reverse the rest of the list first and then make the current node point to its previous node. This approach implicitly uses the call stack to keep track of the nodes as we recursively process them.
87+
88+
Steps:
89+
1. If the head is None or head.next is None, return head (base case: the list is empty or has one node).
90+
2. Recursively reverse the rest of the list: rest = reverseList(head.next).
91+
3. After reversing the rest, set head.next.next = head to make the next node point back to the current node.
92+
4. Set head.next = None to avoid a cycle in the reversed list.
93+
5. Return the rest, which is the new head of the reversed list.
94+
95+
##### Visualization:
96+
For head = [1, 2, 3, 4, 5]:
97+
98+
```perl
99+
Recursive call: reverseList([2, 3, 4, 5])
100+
Recursive call: reverseList([3, 4, 5])
101+
...
102+
103+
Base case:
104+
reverseList([5]) → return [5]
105+
106+
Backtrack:
107+
4.next.next = 4 → list becomes [5, 4]
108+
3.next.next = 3 → list becomes [5, 4, 3]
109+
...
110+
```
111+
##### Time Complexity:
112+
O(n), where n is the number of nodes in the list. We traverse the list once.
113+
##### Space Complexity:
114+
O(n), because of the recursive call stack that grows to a depth of n.
115+
##### Python Code:
116+
```python
117+
class ListNode:
118+
def __init__(self, val=0, next=None):
119+
self.val = val
120+
self.next = next
121+
122+
def reverseList(head: ListNode) -> ListNode:
123+
# Base case: if the list is empty or has only one node, return head
124+
if not head or not head.next:
125+
return head
126+
127+
# Recursively reverse the rest of the list
128+
rest = reverseList(head.next)
129+
130+
# Make the next node point back to the current node
131+
head.next.next = head
132+
# Set the current node's next to None (it becomes the new tail)
133+
head.next = None
134+
135+
# Return the new head (the result of reversing the rest)
136+
return rest
137+
```
138+
## Summary
139+
140+
| Approach | Time Complexity | Space Complexity |
141+
|-----------------------------------|-----------------|------------------|
142+
| Iterative | O(n) | O(1) |
143+
| Recursive | O(n) | O(n) |
144+
145+
Both the iterative and recursive solutions have the same time complexity, but the iterative solution is more space-efficient since it doesn't use the call stack.

0 commit comments

Comments
 (0)