Time complexity of Single Link List Insertion and deletion - c++

I am a bit confused about time complexity of Linked Lists. In this article here it states that insertion and deletion in a linked list is O(1). I wanted to know how this is possible ? Is it assumed that the forward and next pointers are known ? Wouldn't that be Double Linked List then ? I would appreciate it if someone could clarify this . And how the time complexity of insertion/deletion of single linked list is O(1) ?

Is it assumed that the forward and next pointers are known ?
In singly linked lists, for both insertion and deletion, you need a pointer to the element before the insertion/deletion point. Then everything works out.
For example:
# insert y after x in O(1)
def insert_after(x, y):
y.next = x.next
x.next = y
# delete the element after x in O(1)
def delete_after(x):
x.next = x.next.next
For many applications it is easily possible to carry the predecessor of the item you are currently looking at through your algorithm, to allow for dynamic insertion and deletion in constant time. And of course you can always insert and delete at the front of the list in O(1), which allows for a stack-like (LIFO) usage pattern.
Deleting an item when you just know the pointer to the item is generally not possible in O(1). EDIT: As codebeard demonstrates, we can insert and delete by just knowing a pointer to the insertion/deletion point. It involves copying the data from the successor, thus avoiding fixing up the next pointer of the predecessor.

Yes, it's assuming that you already know the place at which you want to insert the data.
Suppose you have some item p in the list, and you want to insert a new element new after p in the list:
new->next = p->next;
p->next = new;
Alternatively, suppose you want to insert new before p. This can still be done in O(1) time:
if (p == head) {
new->next = head;
head = new;
} else {
tmp = p->data;
p->data = new->data;
new->data = tmp;
new->next = p->next;
p->next = new;
}
As for deleting items in a conventional singly linked list, it's not strictly O(1)!
It is O(1) for deleting any element except the last element. If you are trying to delete the last element in a singly linked list, you need to know the element before it (which requires O(N) time assuming you didn't know it before).
To delete the item p:
free_if_necessary(p->data);
if (p->next) {
/* O(1) */
nextnext = p->next->next;
nextdata = p->next->data;
destroy_if_necessary(p->next);
p->data = nextdata;
p->next = nextnext;
} else if (p == head) {
destroy_if_necessary(p);
head = NULL;
} else {
/* O(n) */
prev = find_prev(head, p);
destroy_if_necessary(p);
prev->next = NULL;
}

Maybe this is relative to the delete and insert operation for array.
And there is a prerequisite that you know the postion where to insert or delete.
In array, when you want to insert or delete an element at positon pos, you should move the other elements after the position pos, so the complexity is O(N).
But in List, when you do the same operation, you needn't consider the other elements, so the complexity is O(1).

Related

How to reverse a linked list

I was working on Stack Overflow and came up across the #92.
Reverse a linked list 2 question. This is the question's description:
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. For example, given
the linked list [1,2,3,4,5] 1, 4 the list should become [4,2,3,1,5]
It works for all runs but my code produces the wrong answer for one test case, which doesn't make sense. given. [1,2,3,4] 1,4 with 1 being left position an 4 being right position what makes sense for the list to become is [4,2,3,1] which is what my code produces. The correct answer they display is [4,3,2,1] which is screwing with my head and I can't understand why it's doing that. Any heads up are appreciated.
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right)
{
int x = 1;
ListNode * left_node = nullptr;
ListNode * right_node = nullptr;
ListNode * curr = head;
int temp = 0;
if (!head)
return nullptr;
while(curr)
{
if(x == left)
left_node = curr;
if(x == right)
right_node = curr;
curr = curr->next;
++x;
}
temp = left_node->val;
left_node->val = right_node->val;
right_node->val = temp;
return head;
}
Your solution implements swapping the values for the 2 indexes left and right, which matches the example given in the question.
But the question was to reverse the list between left and right, not just swap the two end points. So 1,2,3,4 becoming 4,3,2,1 is the correct solution and both the example and your solution are wrong, they only swap the entpoints.
PS: Most languages start counting at 0.
#Goswin Von Brederlow has already pointed out what is wrong, I will add a visual representation of what you are doing. Can you spot the problem?
EDIT: You can look in the comments for a better solution in terms of time and space (and also for a worse one).
After the while you should traverse the linked list. Since you don't have a "prev" (no double linked list) you need to traverse it multiple times to reverse it. I prepared a visualization of the algo, which uses a copy from left to right. And a support node for the prev node at each iteration. It uses idx and oidx to keep track of the position in the original and copied list.
And here are the steps of the iteration:

Sorting a singly Linked List

void NodeList::sortNodeAscending()
{
Node* swap = NULL;
Node* saveLink = NULL;
for(Node* firstPointer = head; firstPointer;firstPointer = firstPointer->next)
for(Node* secondPointer = firstPointer->next; secondPointer;secondPointer = secondPointer->next)
{
if((secondPointer->studentId)<(firstPointer->studentId))
{
swap =firstPointer;
saveLink = secondPointer->next;
firstPointer = secondPointer;
secondPointer = swap;
firstPointer->next = secondPointer;
secondPointer->next = saveLink;
}
}
}
This is my code for sorting but the problem that i have is after sorting, all the values are correct but the head is not changed or sorted.
Output:
{ 5, 0, 1, 2, 3, 4, 12, 15}
All the elements are sorted except the first node.
Note: I have already checked the question in the link below and it is different from my question. Sorting a Singly Linked List With Pointers
You are not updating head to point to the new start of the list.
At the end of the first iteration of the outer loop, firstPointer will point to the smallest element and can be updated to the new head.
Sorting of list is not as easy as it appears in your code:
[prev1] [first] [next1] ... [prev2] [second] [next2]
should look like this upon swapping first with second:
[prev1] [second] [next1] ... [prev2] [first] [next2]
How to do that:
Store next1 or next2, and keep track of prev1 and prev2 all the time as you can't look back in singly linked list.
first->next = next2 and second->next = next1
prev1->next = second and prev2->next = first
During all this, you need to take special care for list's head.
As pointed out by Kenny Ostrom, even if the head issues are fixed, if the initial sequence of student id's is 5,3,1,4,2, then the sort ends up with 5,1,3, not sorted and losing two values.
The code is swapping firstPointer and secondPointer, which are used as the inner and outer loop variables. This creates a problem.
When swapping nodes in a linked list, the nodes may be adjacent (three next pointers rotated) or they may be non-adjacent (two pairs of next pointers swapped). To handle both cases with the same code, first swap whatever points to the two nodes to be swapped first (like swap prev1->next with prev2->next), then swap the two node's next pointers (like swap curr1->next with curr2->next).
Trying to use bubble sort or any swapping sort to sort a linked list is complicated because you need to keep track of two previous pointers to nodes as well as two current pointers to nodes.
It would be simpler to start off with an empty "sorted" list, then remove one node at a time from the original list, and insert the node into it's proper location into the "sorted" list. For this method, start off with Node * sorted = NULL; Once all nodes have been removed from the original list and inserted in order into "sorted", then set head = sorted, so that head points to the sorted list.
A much faster method to sort a linked list uses a small (25 to 32) array of pointers to nodes combined with a bottom up merge sort, but that is more than what is needed in this case.

Time Limit Exceeded for Merge k Sorted Lists(leetcode)

merge-k-sorted-lists
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
My code:
ListNode *mergeTwoLists(ListNode *p1, ListNode *p2) {
ListNode dummy(-1);
ListNode *head = &dummy;
while(p1 != nullptr && p2 != nullptr) {
if (p1->val < p2->val) {
head->next = p1;
head = head->next;
p1 = p1->next;
} else {
head->next = p2;
head = head->next;
p2 = p2->next;
}
}
if (p1 != nullptr) {
head->next = p1;
}
if (p2 != nullptr) {
head->next = p2;
}
//head->next = nullptr;
return dummy.next;
}
ListNode *mergeKLists(vector<ListNode *> &lists) {
if (lists.size() == 0) return nullptr;
if (lists.size() == 1) return lists[0];
ListNode *p1, *p2, *p;
while (lists.size() > 1) {
p1 = lists.back();
lists.pop_back();
p2 = lists.back();
lists.pop_back();
p = mergeTwoLists(p1, p2);
lists.push_back(p);
}
return lists[0];
}
I always get Time Limit Exceeded. How should i change the program?
What your are doing has complexity O(nk^2) where n is the size of each array. You merge two lists at a time. Why ? you merge first two lists it takes 2n operations also the size of the first two combined is 2n. Now you merge this with the third, the array size becomes 3n and 3n operations are done, so total number of operations are 2n+3n+....kn ( arithmetic progression ) which is O(nk^2). Instead take a priority queue ( min heap ) insert first elements of all k lists. Now each time take the smallest element from priority queue ( put this in your new list ), remove it from the priority queue and insert the next element of the list to which this element belonged. As all elements are inserted and deleted from priority queue once and in total there are nk elements the complexity is O(nklog(k)). ( Time to delete / insert ) priority queue is O(log(number_of_elements_in_queue)). And in the queue at maximum there are k elements at any time.
For a more detailed explanation plus a code have a look here : Merging k sorted lists. I assume this would be enough to get AC on leetcode :).
Your problem is that you are doing unbalanced merges. If each list has n elements to start with and merge(a,b) means you merge lists of length a and b (which takes time O(a+b)), then the operations you are doing are
merge(n,n)
merge(2n,n)
merge(3n,n)
merge(4n,n)
....
and so you're paying a lot of cost iterating over the long list so many times; with k elements you're doing about (1/2) k^2 n work.
You could look for a specialized imbalance merging algorithm, but a much easier approach would be to just reorganize your work to merge lists of similar size. If you started with k lists each of n elements, then you would do
k/2 instances of `merge(n,n)`
k/4 instances of `merge(2n,2n)`
...
1 instance of `merge(nk/2, nk/2)`
Each step takes nk time, and there are lg(k) steps, for a total cost of nk lg(k).
If k isn't a power of 2 or the lists are not all the same length, there are lots of things you can do to try and minimize the overall amount of work, but a very simple way is to make lists a deque instead of a vector, and for each merge you pop two lists of the back and push the result in the front instead of the back. Another simple optimization on this is to first sort the lists by length.
The other answer is likely better when k is not too large. When k is rather large you're probably better off with a hybrid algorithm: you pick an appropriate m and you organize the total work as I've described, but rather than merging 2 lists at a time, you merge m lists at a time.
My first two guesses at an appropriate m are ceil(sqrt(k)) and the largest value for which the other answer's algorithm is efficient for an m-way merge.
(if for some strange reason m is still very large, then you do the m-way merge with the hybrid algorithm)
Why do I make the predictions above? The other answer only makes one pass through the data, so as long as your CPU can efficiently maintain a priority queue of length k as well as read from k lists at the same time, it is surely better than my algorithm which makes many passes through the data.
But when k gets too large, you run into problems:
Your TLB might not have enough entries to read from k lists at a time
Your cache might not be big enough to store a cache line or two from all of k of the lists as well as fit a priority queue
cache misses and especially TLB misses will degrade performance. The hybrid algorithm reorganizes the work so that you keep the benefit of my algorithmic approach (balanced merges) while nearly all of the work is done with the efficient m-way merge from the other answer.

Binary tree interview: implement follow operation

I was asked to implement a binary search tree with follow operation for each node v - the complexity should be O(1). The follow operation should return a node w (w > v).
I proposed to do it in O(log(n)) but they wanted O(1)
Upd. It should be next greater node
just keep the maximum element for the tree and always return it for nodes v < maximum.
You can get O(1) if you store pointers to the "next node" (using your O(log(n) algorithm), given you are allowed to do that.
How about:
int tree[N];
size_t follow(size_t v) {
// First try the right child
size_t w = v * 2 + 1;
if(w >= N) {
// Otherwise right sibling
w = v + 1;
if(w >= N) {
// Finally right parent
w = (v - 1) / 2 + 1;
}
}
return w;
}
Where tree is a complete binary tree in array form and v/w are represented as zero-based indices.
One idea is to literally just have a next pointer on each node.
You can update these pointers in O(height) after an insert or remove (O(height) is O(log n) for a self-balancing BST), which is as long as an insert or remove takes, so it doesn't add to the time complexity.
Alternatively, you can also have a previous pointer in addition to the next pointer. If you do this, you can update these pointers in O(1).
Obviously, in either case, if you have a node, you also have its next pointer, and you can simply get this value in O(1).
Pseudo-code
For only a next pointer, after the insert, you'd do:
if inserted as a right child:
newNode.next = parent.next
parent.next = newNode
else // left child
predecessor(newNode)
For both next and previous pointers:
if inserted as a right child:
parent.next.previous = newNode
newNode.next = parent.next
parent.next = newNode
else // left child
parent.previous.next = newNode
newNode.previous = parent.previous
parent.previous = newNode
(some null checks are also required).

How can I sort a singly linked list in constant space? [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I have a singly linked list and I need to sort it in constant space due to memory limitations (in other words, no extra space should be used that is proportional to the number of items in the list).
The structure of the linked list is:
head.item = the payload you want to sort on; and
head.next = the next item.
The requirement for constant space discounts solutions where I build another list, I need to do it in-place.
How can I do that?
Sorting a linked list in constant space is easy, you just have to adjust the pointers. The easiest way to do this is to use a sort algorithm that only swaps adjacent elements. I'm going to provide a bubble-sort, just because you've made no requirement for efficiency:
# Enter loop only if there are elements in list.
swapped = (head <> null)
while swapped:
# Only continue loop if a swap is made.
swapped = false
# Maintain pointers.
curr = head
next = curr.next
prev = null
# Cannot swap last element with its next.
while next <> null:
# Swap if items in wrong order.
if curr.item > next.item:
# Notify loop to do one more pass.
swapped = true
# Swap elements (swapping head is special case).
if curr == head:
head = next
temp = next.next
next.next = curr
curr.next = temp
curr = head
else:
prev.next = curr.next
curr.next = next.next
next.next = curr
curr = next
endif
endif
# Move to next element.
prev = curr
curr = curr.next
next = curr.next
endwhile
endwhile
A few methods:
use bubble sort on the list in place, this should be fast enough if the list is small
copy the list to an array and use heapsort or quicksort then copy it back
use bogosort on the list in place. The best case complexity is O(n) so it should be really fast*
*note that the expected runtime complexity is O(n*n!)
For in-place sorting of a linked list, I would suggest merge sort. It's stable, and runs in NlgN time.