Reverse a sublist of a doubly linked list - algorithm

I recently came up with a problem, and with some reading I understood that there's a way to reverse a doubly linked list in constant time, just by swapping head and tail pointers.
Now, thinking of a slightly different version of the problem, if we want to reverse just a sublist of the doubly linked list (from x to y), how could it be done?
0->1->2->3->4->5->6->7->8->9 with (x = 2, y = 7)
would become:
0->1->7->6->5->4->3->2->8->9

You can but you mess the linked list a lot.
Basically we add a bit to each node that says if you should reverse order at this point.
Next you modify the iterator to xor all these new bits it's seen so far.
The iterator will now pick next/prev or prev/next depending on the xor of the bits.
Now you can reverse arbitrary intervals in the linked list in O(1), BUT you loose iterator consistency: any swap operation might invalidate existing iterators. Also the code will be very confusing so I would avoid it if at all possible.
To do the actual flip you change (add null-checking to make sure you don't crash)
(first->prev,last->next) = (last->next, first->prev)
last->next->next = last
first->prev->prev = first
last.reverse_here = !last.reverse_here
first->prev.reverse_here = !first->prev.reverse_here
Invalidate_all_existing_iterators()
Draw it on paper because it's very confusing.

way to reverse a doubly linked list in constant time
It is not possible, however it is possible in linear time O(n)
Answer to question : You have to manage "start" and "end" differently than the rest. If you start from 7 and goes to 2, you remember this pointer 7-> and store it in variable X. It stores pointer to 8. Then you continue as usual.
When you reach the 2, you put 2.head = X.
The similar thing must be done for 1->7, but you should figure it out.
Edit about constant time : you can rewrite iterator in constant time, however the data structure itself remains unchanged. Therefore with only changing iterator, you cant take reversed data itself and do some stuff with it - because it is not really reversed.
PS : In arraylist you can change iterator in constant time even for sublist.

Related

Finding the kth last element of a singly linked list: answer explanation

When you need to find the kth last element of a singly linlked list, the usual naive approach is to perform two passes. The first to find the length of the list and the second to iterate until the (length-k)th element.
Whereas the optimized version takes advantage of two pointers:
p1 refering to the head of the list
p2 being kth elements ahead of p1
This allows us to return p1's element when p2 reaches the end of the list.
I don't understand why the second approach is faster than the first when in both cases we have one pointer iterating all over the list and another until the (length-k)th element.
Is it due to cache optimization?
Thanks.
If you keep p2 exactly k elements behind p1, then it doesn't really help much, since you have to do the same number of traversals all together.
You can optimize the procedure by using more pointers, though.
As you walk though the list, lets say you remember the pointer at every (k/m)th position, for some m. You only need to remember the last m+1 of those pointers. Then, when you get to the end of the list, instead of iterating again from the beginning, start at the oldest pointer you remembered. It will be between k and k + (k/m) elements behind the end, so you only have to move it forward by at most k/m positions.
Consider non-uniform memory access times and a singly linked list of length n:
- in the counted iteration approach, accesses to the same node will be n accesses apart
- in the lagging pointer approach, accesses to the same node will be k accesses apart
With an LRU cache (/with each LRU cache level), the former is more likely to induce capacity misses than the latter.

Data structures linked list

I was asked to solve this out:
There are two singly linked lists.
I need to write a method which get those two linked lists and returns a pointer to the starting point where the suffix is the same in those two lists.
Example:
given:
1->2->4->6->8->10->15
2->4->8->10->15
the returned value would be a pointer to the member - 8.
But,
I need to do it without changing the lists or using more memory,
and - we need to scan the lists only once, means T(n)=O(n).
measure the lengths of both lists
skip forward in the longer list until they're the same length
walk forward through both lists in step, and remember the nodes AFTER the last point where the lists have different elements. These are the pointers you can return.
Now... I know you said that you only want to scan the lists once, and I have done it twice, but you also said that this means T(n) = O(n), and that is not correct.
Scanning the lists twice is also in O(n) and is required to solve the problem without using unbounded extra memory.
This is a psuedocode.. not Python code for that matter
take the two lists and let p1 point to longer list and p2 point to shorter list,
while p1->next!=NULL:
if p1->value = p2->value:
p1 = p1->next
p2 = p2->next
else:
p1 = p1->next
returnpointer = p1->next// if it happens that some elements are same towards end but not the last element.. p1->next would be pointing to NULL anyways and else.. it'll always be the element next to the last element which wasn't same
return returnpointer

Getting the nth to last element in a linked list

We have a linked list of size L, and we want to retrieve the nth to the last element.
Solution 1: naive solution
make a first pass from the beginning to the end to compute L
make a second pass from the beginning to the expected position
Solution 2: use 2 pointers p1, p2
p1 starts iterating from the beginning, p2 does not move.
when there are n elements between p1 and p2, p2 starts iterating as well
when p1 arrives at the end of the list, p2 is at the expected position
Both solutions seem to have the same time complexity (i.e, 2L - n iterations over list elements)
Which one is better?
Both those algorithms are two-pass. The second may have better performance for reasonably small n because the second pass accesses memory that is already cached by the first pass. (The passes are interleaved.)
A one-pass solution would store the pointers in a circular buffer or queue, and return the "head" of the queue once the end of the list is reached.
How about using 3 pointers p, q, r and a counter.
Iterate through the list with p updating the counter.
Every n nodes assign r to q and q to p
When you hit the end of the list you can figure out how far
r is from the end of the list.
You can get the answer in no more than O(L + n)
If n << L, solution 2 is typically faster, because of caching, i.e. the memory blocks containing p1 and p2 are copied to the CPU cache once and the pointers moved for a bunch of iterations before RAM needs to be accessed again.
Would it not be much cheaper to simply store the length of the linked list in O(1) memory? The only reason you have to do a "first pass" at all is because you don't know the length of your linked list. If you store the length, you could iterate over (|L|-n) elements every time and get retrieve the element easily. For higher values of n in comparison to L, this way would save you substantial amounts of time. For example if n was equal to |L|, you could simply return the head of the list with no iteration at all.
This method uses slightly more memory than your first algorithm since it stores the length in memory, but your second algorithm uses two pointers, whereas this method only uses 1 pointer. If you have the memory for a second pointer, you probably have the memory to store the length of your linked list.
Granted O(|L|-n) is equivalent to O(n) in pure theory, but there are "fast" linear algorithms and then there are "slow" ones. Two-pass algorithms for this kind of problem are slow.
As #HotLicks pointed out in the comments, "One needs to understand that "big O" complexity is only loosely related to actual performance in many cases, since it ignores additive factors and constant multipliers." IMO just go for the laziest method in this case and don't overthink it.

Immutablity of Node-based data structures

Is there any general approach if one wanted to provide an immutable version of e.g. LinkidList, implemented using as a linked sequence of nodes? I understand that in the case of ArrayList you would copy the underlying array, but in this case this is not that obvious to me...
Immutable lists are basically represented the same way as regular linked lists, except that all operations that would normally modify the list return a new one instead. This new list does not neccessarily need to contain a copy of the entire previous list but can reuse elements of it.
I recommend implementing the following operations in the following ways:
Popping the element at the front: simply return a pointer to the next node. Complexity: O(1).
Pushing an element to the front: Create a new node that point to the first node of the old list and return it. O(1).
Concatenating list a with list b: copy the entire list a and let the pointer in the final node point to the beginning of list b. Note that this is faster than the same operation on mutable lists. O(length(a)).
Inserting at position x: Copy everything up to x, add a node with the new element to the back of the copy, and let that node point to the old list at position x + 1. O(x).
Removing the element at position x: practically the same as inserting. O(x).
Sorting: you can just use plain quick- or mergesort. It's not much faster or slower than it would be on mutable lists. The only difference is that you can't sort in place but will have to sort to a copy. O(n*log n).

Find the middle of unknown size list

In a recent interview I was asked:
Find the middle element of a sorted List of unknown length starting from the first position.
I responded with this:
Have 2 position counters:
counter1
counter2
Increment counter1 by 1 and counter2 by 2. When counter 2 reaches the end of the list counter 1 will be in the middle. I feel that this isn't efficient because I am revisiting nodes I have already seen. Either way, is there a more efficient algorithm?
Assuming a linked list, you can do it while visiting arbitrarily-close-to N of the items.
To do 5/4 N:
Iterate over the list until you hit the end, counting the items.
Drop an anchor at every power of 2'th element. Track the last 2 anchors.
Iterate the before-last anchor until it reaches the middle of the list.
When you hit the end of the list, the before-last anchor is before the mid-point but at least half way there already. So N for the full iteration + at most 1/4 N for the anchor = 5/4 N.
Dropping anchors more frequently, such as at every ceiling power of 1.5^th item, gets you as close to N as needed (at the cost of tracking more anchors; but for any given X as the power step, the asymptotic memory is constant).
I assume you're discussing a linked-list. Indeed your solution is an excellent one. An alternative would be to simply traverse the list counting the number of elements, and then start over from the beginning, traversing half the counted amount. Both methods end up traversing 3n/2 nodes, so there isn't much difference.
It's possible there may be slight cache advantages to either method depending on the architecture; the first method might have the advantage of having cached nodes which could mean quicker retrieval if the cache is large enough before the two pointers are two far apart. Alternatively, the cache might get better blocks if we traverse the list in one go, rather than keeping two pointers alive.
Presuming you can detect that you're beyond the end of the list and seek an arbitrary position efficiently within the list, you could keep doubling the presumed length of the list (guess the length is 1, then 2, then 4, ...) until you're past the end of the list, then use a binary search between the last attempted value less than the length of the list and the first value that exceeded the end of the list to find the actual end of the list.
Then, seek the position END_OF_LIST / 2.
That way, you do not have to visit every node.
Technically, you could do this in one traversal if you used O(N) memory (assuming you're using a linked list).
Traverse the list and convert it into an array (of pointers if this is a linked list).
Return arr[N/2]
Edit: I do love your answer though!
Assuming the regular in-memory linked list that allows reading the next element given the reference to the current multiple times (disclaimer, not tested, but the idea should work):
// assume non-empty list
slow = fast = first;
count = 0;
while (fast)
{
fast = fast.next;
if (!fast)
break;
count++;
fast = fast.next;
if (fast)
count++;
slow = slow.next;
}
if (count % 2)
return slow.data;
else
return (slow.data + slow.next.data)/2.0;
A more difficult case is when the "list" is not a linked list in memory, but rather a stream that you can read in sorted order and what to read each element only once for which I do not have a nice solution.

Resources