I'm working on a reverse-linked list problem where you reverse a sub-list within the Linked-list structure. The sublist is defined with left
and right
which is the portion you must reverse. When working with smaller sized Linked Lists (size 2) it works fine but when working with 5 elements or so, it begins to fail. This is because (as seen in the example) I do not quite understand how to maintain the original head and connect to my now reversed sub-list. I have tried head->next = prev;
but this did not work and resulted in nullptr memory access error.
Quite new to this data structure and would appreciate any insight into how I might go about solving the issue.
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy = new ListNode(0, head);
ListNode* prev = nullptr;
ListNode* current = head;
int counter = 1;
while (current != nullptr)
{
if (counter == left)
{
ListNode* lastKnownParent = current;
ListNode* n = nullptr;
while (counter <= right)
{
n = current->next;
current->next = prev;
prev = current;
current = n;
counter ;
}
lastKnownParent->next = current;
}
else {
counter ;
current = current->next;
}
}
dummy->next->next = current;
dummy->next = prev;
return dummy->next;
}
Success with smaller sized:
input: [3,5] left = 1, right = 2: Output: [5,3]
Fail with larger size:
input: [1,2,3,4,5] left = 2, right = 4: Output: [2,4,3,2,5]
CodePudding user response:
There are a few issues:
The lines at the bottom of your function are not good. They hard-code that some change needs to happen at the first two nodes of the list, but this makes no sense when for example the reversal has to take place between nodes 4 and 6. So those two lines should be removed:
dummy->next->next = current; dummy->next = prev;
The node that precedes
lastKnownParent
will have to get itsnext
pointer updated, but the problem is that there is no direct reference anymore to that node. The statements I quoted above were probably meant to reach that node, but it has to be done relative to theleft
position.
To illustrate what goes wrong, look at this visualisation when we have a list of 8 nodes and the two given positions are left=4
and right=6
:
When reaching left
and entering the loop, we get this state:
lastKnownParent
dummy head current n
↓ ↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ 0 ├──►│ 1 ├──►│ 2 ├──►│ 3 ├──►│ 4 ├──►│ 5 ├──►│ 6 ├──►│ 7 ├──►│ 8 │
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
prev == nulltptr
When reaching right
, the loop makes one more iteration and then the loop exits, resulting in this state:
dummy head lastKnownParent prev current n
↓ ↓ ↓ ↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ 0 ├──►│ 1 ├──►│ 2 ├──►│ 3 ├──►│ 4 │◄──┤ 5 │◄──┤ 6 │ │ 7 ├──►│ 8 │
└───┘ └───┘ └───┘ └───┘ └─┬─┘ └───┘ └───┘ └───┘ └───┘
▼
nullptr
Your code then correctly sets the next
pointer of the node with value 4 (lastKnownParent
):
dummy head lastKnownParent prev current n
↓ ↓ ↓ ↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ 0 ├──►│ 1 ├──►│ 2 ├──►│ 3 ├──►│ 4 │◄──┤ 5 │◄──┤ 6 │ │ 7 ├──►│ 8 │
└───┘ └───┘ └───┘ └───┘ └─┬─┘ └───┘ └───┘ └─▲─┘ └───┘
└───────────────────────┘
But there is one thing missing. The link from node 3 to node 6 still needs to be made. This is something you seem to have tried at the very end of your function, but you cannot assume that it is always dummy->next->next
that needs to change. This depends on left
, or in other words, it depends on lastKnownParent
. The problem is that lastKnownParent
points to the successor of the node that needs this update. So, make lastKnownParent
to point to the predecessor instead, at the time it is initialised. Then you can make both updates (first updating node 4, then node 3). Then it would look like this:
dummy head lastKnownParent prev current n
↓ ↓ ↓┌──────────────────────┐↓ ↓ ↓
┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌▼──┐ ┌───┐ ┌───┐
│ 0 ├──►│ 1 ├──►│ 2 ├──►│ 3 │ │ 4 │◄──┤ 5 │◄──┤ 6 │ │ 7 ├──►│ 8 │
└───┘ └───┘ └───┘ └───┘ └─┬─┘ └───┘ └───┘ └─▲─┘ └───┘
└───────────────────────┘
To make it possible to set lastKnownParent
one node earlier, you could for instance have prev
follow behind current
even in the first iterations -- when left
is not yet encountered.
Here is the corrected code, with comments where something changed:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy = new ListNode(0, head);
ListNode* prev = dummy; // prev follows behind current
ListNode* current = head;
int counter = 1;
while (current != nullptr)
{
if (counter == left)
{
ListNode* lastKnownParent = prev; // one node earlier
ListNode* n = nullptr;
while (counter <= right)
{
n = current->next;
current->next = prev;
prev = current;
current = n;
counter ;
}
// lastKnownParent is now one node earlier than in your code
lastKnownParent->next->next = current;
lastKnownParent->next = prev; // now the mutation is complete...
break; // ... so we don't need to iterate the remaining nodes.
}
else {
counter ;
prev = current; // prev follows behind current
current = current->next;
}
}
// Removed two statements here
return dummy->next;
}
Further improvements
This code assumes that the given positions are valid. You may want to improve on this and determine what should happen when one or both are out of range, or right
is less than left
.
You may also want to think of more elegant ways to solve this challenge. For instance, you could create functions that can:
- inverse a complete list
- split a list at a certain position into two smaller lists.
- concatenate smaller lists into one
This may lead to code that is more readable.