Reverse Linked List II
MedPrompt
Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.
Example 1:
Input: head = [1,2,3,4,5], left = 2, right = 4
Output: [1,4,3,2,5]Example 2:
Input: head = [5], left = 1, right = 1
Output: [5]
Constraints:
- The number of nodes in the list is
n. 1 <= n <= 500-500 <= Node.val <= 5001 <= left <= right <= n
Follow up: Could you do it in one pass?
Approaches
3 approaches with complexity analysis and trade-offs.
This is the most optimal approach, solving the problem in a single pass with constant extra space. It involves careful pointer manipulation to reverse the sublist directly within the original list.
Algorithm
- Create a
dummynode and setdummy.next = head. This simplifies handling the edge case whereleft = 1. - Create a pointer
pre_leftand initialize it todummy. - Move
pre_leftforwardleft - 1times. After the loop,pre_leftwill be at the node just before the sublist to be reversed. - Initialize
current = pre_left.next. Thiscurrentpointer marks the start of the sublist to be reversed and will eventually become its tail. - Loop
right - lefttimes. This is the core of the reversal:- a. Let
node_to_move = current.next. This is the node we will move to the front of the reversed section. - b. Detach
node_to_movefrom its current position by settingcurrent.next = node_to_move.next. - c. Prepend
node_to_moveto the reversed part by settingnode_to_move.next = pre_left.next. - d. Update the link from the preceding part of the list by setting
pre_left.next = node_to_move.
- a. Let
- After the loop completes, the sublist from
lefttorightis reversed and correctly linked with the rest of the list. - Return
dummy.next, which is the head of the modified list.
Walkthrough
This iterative approach is the standard and most efficient solution. It directly manipulates the node pointers in-place to achieve the reversal.
- We use a
dummynode to simplify edge cases, such as when the reversal starts at the head of the list (left = 1). Thedummynode points to the originalhead. - First, we iterate
left - 1times to find the node just before the sublist to be reversed. Let's call thispre_left. - We then identify the start of our sublist,
current = pre_left.next. Thiscurrentnode will act as a pivot and will become the tail of the reversed sublist after all operations. - We then loop
right - lefttimes. In each iteration, we take the node immediately followingcurrent(let's call itnode_to_move) and move it to become the new first node of the sublist (i.e., right afterpre_left).
Let's trace [1,2,3,4,5], left=2, right=4:
- Initial:
dummy -> 1 -> 2 -> 3 -> 4 -> 5.pre_leftis at1,currentis at2. - Iteration 1 (move
3):node_to_moveis3. The list becomesdummy -> 1 -> 3 -> 2 -> 4 -> 5. - Iteration 2 (move
4):node_to_moveis4. The list becomesdummy -> 1 -> 4 -> 3 -> 2 -> 5.
After the loop, the sublist is reversed, and all connections are correctly updated. We return dummy.next.
class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if (head == null || left == right) { return head; } ListNode dummy = new ListNode(0); dummy.next = head; // 1. Reach node at position `left - 1` ListNode pre_left = dummy; for (int i = 0; i < left - 1; i++) { pre_left = pre_left.next; } // 2. `current` points to the node at position `left` ListNode current = pre_left.next; // 3. Reverse the sublist for (int i = 0; i < right - left; i++) { ListNode node_to_move = current.next; current.next = node_to_move.next; node_to_move.next = pre_left.next; pre_left.next = node_to_move; } return dummy.next; }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Optimal O(1) space complexity as it only uses a few extra pointers.
Highly efficient single-pass solution with O(N) time complexity.
Handles all edge cases gracefully, especially with the use of a dummy node.
Cons
The pointer manipulation can be complex and non-intuitive to understand and implement correctly without careful visualization.
This approach involves converting the linked list into an array of values, reversing the specified sub-array, and then iterating through the linked list again to update the node values from the modified array.
Algorithm
- If
headis null orleft == right, there's nothing to reverse, so returnhead. - Create an
ArrayList<Integer>to store the values of the nodes. - Traverse the linked list from the
headwith a pointer,current, and add the value of each node (current.val) to theArrayList. - After the list is fully traversed, the
ArrayListcontains all node values in order. - Reverse the sub-array within the
ArrayListfrom indexleft - 1toright - 1. This can be done with a two-pointer approach, swapping elements from both ends of the sub-array until the pointers meet. - Reset the
currentpointer back to theheadof the linked list. - Iterate through the
ArrayListfrom the beginning, and for each value, update the corresponding node in the linked list:current.val = arraylist.get(i). Movecurrentto the next node. - Return the original
headof the list, which now contains the modified values.
Walkthrough
This is a straightforward but less efficient approach. The core idea is to avoid complex pointer manipulation by offloading the reversal logic to a more convenient data structure, an array.
- First, we traverse the entire linked list and store the value of each node in a dynamic array (like
ArrayListin Java). - Once all values are in the array, we reverse the portion of the array from index
left - 1toright - 1. This can be done using a standard two-pointer swapping technique. - Finally, we traverse the linked list a second time from the head. In this traversal, we update the value of each node with the corresponding value from our modified array.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if (head == null || left == right) { return head; } // 1. Copy list values to an ArrayList List<Integer> values = new ArrayList<>(); ListNode current = head; while (current != null) { values.add(current.val); current = current.next; } // 2. Reverse the sublist of values int l = left - 1; int r = right - 1; while (l < r) { int temp = values.get(l); values.set(l, values.get(r)); values.set(r, temp); l++; r--; } // 3. Update the linked list with the new values current = head; for (int val : values) { current.val = val; current = current.next; } return head; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Conceptually simple and easy to implement.
Avoids the complexity of direct pointer manipulation in a linked list.
Cons
Requires O(N) extra space to store the node values, which is inefficient for large lists.
Requires two full passes over the data: one to build the array and another to update the list values.
It only modifies the values within the nodes, not the actual pointers. This might not be acceptable if the nodes contain more complex data or if pointer manipulation is a requirement.
This approach uses recursion to solve the problem. The main idea is to peel off nodes one by one until we reach the left-th position. Once there, we use another recursive helper function to reverse right - left + 1 nodes.
Algorithm
- The main function
reverseBetween(head, left, right)serves as the entry point. - Base Case: If
left == 1, it means the reversal starts from the currenthead. We then call a helper function,reverseN(head, right), which is responsible for reversing the firstrightnodes of a list. - Recursive Step: If
left > 1, the currentheadis not part of the sublist to be reversed. We keep it in place and make a recursive call on the rest of the list:head.next = reverseBetween(head.next, left - 1, right - 1). This effectively moves our 'window' of reversal one step down the list. - The helper function
reverseN(head, n)reverses the firstnnodes:- It uses a class-level variable
successorto keep track of the node that comes after the sublist being reversed. - Base Case: If
n == 1, we've reached the end of the sublist. We storehead.nextinsuccessorand returnhead. - Recursive Step: It calls
reverseN(head.next, n - 1). When this call returns, it provides the new head of the reversed tail (last). We then rewire the pointers:head.next.nextpoints back tohead, andhead.nextpoints to the savedsuccessor. - It returns
last, which propagates the new head of the reversed section up the call stack.
- It uses a class-level variable
Walkthrough
This approach leverages the call stack to manage the state of the reversal. It's an elegant way to think about the problem by breaking it down into smaller, self-similar subproblems.
The solution is broken down into two parts:
- A main recursive function
reverseBetween(head, left, right)that navigates to the start of the sublist to be reversed. - A helper recursive function
reverseN(head, n)that reverses the firstnnodes of a given list.
The reverseBetween function decrements left and right and calls itself on head.next until left becomes 1. This means we have reached the starting node for reversal. When left is 1, it calls reverseN(head, right) to perform the actual reversal of right nodes.
The reverseN function works by recursively reversing n-1 nodes from head.next. On the way back up the call stack, it reverses the pointers. A key detail is to save the (n+1)-th node (the successor) before the recursion, so the reversed sublist can be correctly linked back to the rest of the list.
class Solution { private ListNode successor = null; // Reverses the first n nodes of the list starting at head private ListNode reverseN(ListNode head, int n) { if (n == 1) { // Base case: reached the end of the sublist to reverse successor = head.next; // Save the node after the sublist return head; } // Recurse on the rest of the list ListNode last = reverseN(head.next, n - 1); head.next.next = head; // Connect the reversed part to the successor head.next = successor; return last; } public ListNode reverseBetween(ListNode head, int left, int right) { if (left == 1) { // If reversal starts from the head, just reverse the first 'right' nodes return reverseN(head, right); } // Recurse until we reach the starting point of reversal // The subproblem is to reverse the list starting from head.next head.next = reverseBetween(head.next, left - 1, right - 1); return head; }}Complexity
Time
O(N)
Space
O(N)
Trade-offs
Pros
Provides an elegant and concise recursive structure that mirrors the problem's definition.
Manipulates pointers directly, preserving the original node objects.
Cons
Uses O(N) space for the recursion call stack in the worst case, which is suboptimal compared to the iterative solution.
Can be harder to reason about and debug than an iterative approach for those not comfortable with recursion.
The use of a shared
successorvariable (as a class member or passed by reference) can be considered a side effect, which can make the code less clean.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * public int val; * public ListNode next; * public ListNode(int val=0, ListNode next=null) { * this.val = val; * this.next = next; * } * } */ public class Solution { public ListNode ReverseBetween ( ListNode head , int left , int right ) { if ( head . next == null || left == right ) { return head ; } ListNode dummy = new ListNode ( 0 , head ); ListNode pre = dummy ; for ( int i = 0 ; i < left - 1 ; ++ i ) { pre = pre . next ; } ListNode p = pre ; ListNode q = pre . next ; ListNode cur = q ; for ( int i = 0 ; i < right - left + 1 ; ++ i ) { ListNode t = cur . next ; cur . next = pre ; pre = cur ; cur = t ; } p . next = pre ; q . next = cur ; return dummy . next ; } }import java.util.Stack ; public class Reverse_Linked_List_II { /** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ public class Solution_optimize { public ListNode reverseBetween ( ListNode head , int m , int n ) { ListNode dummy = new ListNode ( 0 ); ListNode prev = dummy ; // I missed this one prev . next = head ; ListNode p = head ; for ( int i = 1 ; i < m ; i ++) { prev = p ; p = p . next ; } // @note:@memorize: now p is pointing to m-th node ListNode originalFirst = p ; for ( int i = m ; i < n ; i ++) { ListNode currentHead = prev . next ; ListNode futureHead = originalFirst . next ; // swap // prev.next = currentHead.next; prev . next = futureHead ; // currentHead.next = futureHead.next; originalFirst . next = futureHead . next ; futureHead . next = currentHead ; } return dummy . next ; } } public class Solution { public ListNode reverseBetween ( ListNode head , int m , int n ) { if ( m > n ) { return reverseBetween ( head , n , m ); } int diff = n - m ; if ( diff == 0 ) { return head ; } ListNode p1 = head ; // set diff for both pointers // @note: corner case: n-m > list-length while ( diff > 0 && p1 != null ) { p1 = p1 . next ; diff --; } ListNode dummy = new ListNode ( 0 ); dummy . next = head ; ListNode prev = dummy ; ListNode p2 = head ; int mm = m ; while ( mm - 1 > 0 ) { prev = prev . next ; p1 = p1 . next ; p2 = p2 . next ; mm --; } ListNode nextRecord = p1 . next ; diff = n - m ; // start reverse from p1 to p2 // 1->2->3->4->5 Stack < ListNode > sk = new Stack <>(); while ( p2 != p1 . next ) { // @note: here, when p1==p2 should enter loop sk . push ( p2 ); p2 = p2 . next ; } while (! sk . isEmpty ()) { prev . next = sk . pop (); prev = prev . next ; } prev . next = nextRecord ; return dummy . next ; } } } ############ /** * 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 reverseBetween ( ListNode head , int left , int right ) { if ( head . next == null || left == right ) { return head ; } ListNode dummy = new ListNode ( 0 , head ); ListNode pre = dummy ; for ( int i = 0 ; i < left - 1 ; ++ i ) { pre = pre . next ; } ListNode p = pre ; ListNode q = pre . next ; ListNode cur = q ; for ( int i = 0 ; i < right - left + 1 ; ++ i ) { ListNode t = cur . next ; cur . next = pre ; pre = cur ; cur = t ; } p . next = pre ; q . next = cur ; return dummy . next ; } }/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @param {number} left * @param {number} right * @return {ListNode} */ var reverseBetween = function (head, left, right) { if (!head.next || left == right) { return head; } const dummy = new ListNode(0, head); let pre = dummy; for (let i = 0; i < left - 1; ++i) { pre = pre.next; } const p = pre; const q = pre.next; let cur = q; for (let i = 0; i < right - left + 1; ++i) { const t = cur.next; cur.next = pre; pre = cur; cur = t; } p.next = pre; q.next = cur; return dummy.next; };# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution : def reverseBetween ( self , head : Optional [ ListNode ], left : int , right : int ) -> Optional [ ListNode ]: if head . next is None or left == right : return head dummy = ListNode ( 0 , head ) pre = dummy for _ in range ( left - 1 ): pre = pre . next p , q = pre , pre . next cur = q # +1, so when for loop done, 'cur' is at right's next node for _ in range ( right - left + 1 ): t = cur . next cur . next = pre pre , cur = cur , t p . next = pre # p,q did not change by the for loop, but now pre at right(or called m) node, cur at right+1 node q . next = cur return dummy . next ############ class Solution_optimize : def reverseBetween ( self , head : ListNode , m : int , n : int ) -> ListNode : dummy = ListNode ( 0 ) prev = dummy prev . next = head p = head for i in range ( 1 , m ): prev = p p = p . next original_first = p # this is anchor node always pointing to the next-to-be-swapped for i in range ( m , n ): current_head = prev . next future_head = original_first . next prev . next = future_head original_first . next = future_head . next future_head . next = current_head return dummy . next ############ # Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None class Solution ( object ): def reverseBetween ( self , head , m , n ): """ :type head: ListNode :type m: int :type n: int :rtype: ListNode """ def reverse ( root , prep , k ): cur = root pre = None next = None while cur and k > 0 : next = cur . next cur . next = pre pre = cur cur = next k -= 1 root . next = next prep . next = pre return pre dummy = ListNode ( - 1 ) dummy . next = head k = 1 p = dummy start = None while p : if k == m : start = p if k == n + 1 : reverse ( start . next , start , n - m + 1 ) return dummy . next k += 1 p = p . next// OJ: https://leetcode.com/problems/reverse-linked-list-ii/ // Time: O(N) // Space: O(1) class Solution { public: ListNode * reverseBetween ( ListNode * head , int m , int n ) { ListNode dummy , * p = & dummy ; dummy . next = head ; for ( int i = 1 ; i < m ; ++ i ) p = p -> next ; auto q = p -> next , tail = q ; for ( int i = m ; i <= n ; ++ i ) { auto node = q ; q = q -> next ; node -> next = p -> next ; p -> next = node ; } tail -> next = q ; return dummy . next ; } };Video 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.