How to properly understand Space Complexity of an Algorithm? - algorithm

I am solving a LeetCode Question to reverse nodes in K Group of a Linked List.
I have written the following program to reverse and its working fine.
package com.sample.testapp
class LinkedListPractice {
class ListNode(val data: Int) {
var next: ListNode? = null
}
companion object {
#JvmStatic
fun main(args: Array<String>) {
var root: ListNode = ListNode(1)
root.next = ListNode(2)
root.next!!.next = ListNode(3)
root.next!!.next?.next = ListNode(4)
root.next!!.next?.next?.next = ListNode(5)
println("Initial")
prinLinkedList(root)
var k = 2
val answer = reverseKNodes(root, k)
println()
println("After reverse")
prinLinkedList(answer)
}
fun prinLinkedList(root: ListNode?) {
var current = root
while (current != null) {
print(current.data)
current = current.next
print("->")
}
}
private fun reverseKNodes(root: ListNode?, k: Int): ListNode? {
if (root == null) return null
//reverse first k nodes and then call for next recursively until linkedlist ends
var counter = 0
var pre: ListNode? = null
var next: ListNode? = null
var current = root
while (current != null && counter < k) {
next = current?.next
current?.next = pre
pre = current
current = next
counter++
}
if (next != null) {
root.next = reverseKNodes(next, k)
}
return pre
}
}
}
I am just confused in calculating Space Complexity for this program.
As per my understanding i am just creating pointers to nodes and reversing them so space complexity should be constant O(1), but i have a doubt that the ListNode pre and ListNode next are new and then value is assigned to them.
So as we are traversing all the K-Group a new pre node is created every time which we return. So should i call its space complexity as O(N) ?
Can somebody please help me clearing this doubt?
Any help will be appreciated.

Space complexity estimates the size of (an abstract notion of) memory an algorithm needs to run, measured in terms of (a standard notion of) input size.
Your method implements a list reversal in chunks of k elements, k being a constant, ie. independent of the input size. Therefore, although you only handle single a single list node and its adjacency linkage at a time, your code is O(n), n being the list length, as you have floor(n/k) recursive calls each of which takes constant space on the stack.
Of course, you may bet on your compiler's optimizer to transform the tail recursion into a loop after which your code would indeed have space complexity O(1).
Note that very strictly speaking the memory requirement of a single pointer is not O(1) but O(log n) as no fixed pointer size could support an arbitrarily large linked list, but this consideration is typically abstracted away.
ListNode pre and ListNode next
These are only placeholders, ie represent a fixed amount of memory. While their content changes over the course of running the algorithm, the memory footprint does not.

As per my understanding i am just creating pointers to nodes and reversing them so space complexity should be constant O(1)
It would be constant if there was no recursion. But every recursive execution context gets its own set of variables -- being the arguments root and k and the other locals counter, pre, next and current. As there are ceil(n/k) recursive calls happening, there are just as many of those six variables (which each take up a constant amount of memory), giving a space complexity of O(n/k).
I have a doubt that the ListNode pre and ListNode next are new and then value is assigned to them.
They are local variables. Using the word "new" here is a bit ambiguous, as we should not think of new instances of nodes. The only memory they take is for storing the object reference (pointer).
So as we are traversing all the K-Group a new pre node is created every time which we return.
No new nodes are created (there is no Node() constructor call in your code). The reference that is returned, is always an existing reference, which is used to overwrite the old reference in a next property.
So should I call its space complexity as O(N) ?
It is O(n/k)
To make it O(1), turn the recursion into a loop. This requires a few more variables:
private fun reverseKNodes(root: ListNode?, k: Int): ListNode? {
var counter = 0
var pre: ListNode? = null
var next: ListNode? = null
var newRoot: ListNode? = null
var tail: ListNode? = null
var prevTail: ListNode? = null
var current = root
while (current != null) {
tail = current
counter = 0
pre = null
next = null
while (current != null && counter < k) {
next = current?.next
current?.next = pre
pre = current
current = next
counter++
}
if (newRoot == null) {
newRoot = pre
} else {
prevTail?.next = pre
}
prevTail = tail
}
return newRoot
}
Note that this is not the solution to the LeetCode challenge, since that code challenge stipulates:
If the number of nodes is not a multiple of k then left-out nodes, in the end, should remain as it is.
This is not what your code is doing. So you would need to add additional logic to look ahead and check there are still k nodes ahead before doing the actual reversal of a chunk:
fun reverseKGroup(root: ListNode?, k: Int): ListNode? {
var counter = 0
var pre: ListNode? = null
var next: ListNode? = null
var newRoot: ListNode? = null
var tail: ListNode? = null
var prevTail: ListNode? = null
var current = root
var node: ListNode? = null
while (current != null) {
tail = current
counter = 0
pre = null
next = null
node = current
for (i in 1..k) {
if (node == null) {
if (prevTail != null) {
prevTail?.next = current
}
return newRoot ?: root
}
node = node?.next
}
while (current != null && counter < k) {
next = current?.next
current?.next = pre
pre = current
current = next
counter++
}
if (newRoot == null) {
newRoot = pre
} else {
prevTail?.next = pre
}
prevTail = tail
}
return newRoot
}

Related

find out which sorting alogorithm used in this function

Hi can someone help me out which sorting algorithm used in this function
public void sortList() {
Node current = null, index = null;
int temp;
//Check whether list is empty
if(head == null) {
return;
}
else {
//Current will point to head
for(current = head; current.next != null; current = current.next) {
//Index will point to node next to current
for(index = current.next; index != null; index = index.next) {
//If current's data is greater than index's data, swap the data of current and index
if(current.data > index.data) {
temp = current.data;
current.data = index.data;
index.data = temp;
}
}
}
}
}
By the way it is Doubly Link List
The current node is fixed and then iteration is done from next node to the end(via index variable), at the end of one iteration of outer loop the node pointed to by current has correct value, then the current is progressed to next node.
This is Selection Sort, the most elementary sort
Fun fact : although slow because of complexity O(n^2) selection sort can be used when the write operation is expensive because it only swaps max n times for a list of size n

How to find the total number of items in linked list?

I have a linked list which is cyclic and I want to find out the total number of elements in this list. How to achieve this?
One solution that I can think of is maintaining two pointers. First pointer (*start) will always point to the starting node, say Node A.
The other pointer (*current) will be initialized as: current = start->next.
Now, just iterate each node with current -> next until it points to start.
And keep incrementing a counter: numberOfNodes++;
The code will look like:
public int countNumberOfItems(Node* start){
Node* current = start -> next;
int numberOfNodes = 1; //Atleast the starting node is there.
while(current->next != start){
numberOfNodes++;
current = current->next;
}
return numberOfNodes;
}
Let's say the list has x nodes before the loop and y nodes in the loop. Run the Floyd cycle detection counting the number of slow steps, s. Once you detect a meet point, run around the loop once more to get y.
Now, starting from the list head, make s - y steps, getting to the node N. Finally, run two slow pointers from N and M until they meet, for t steps. Convince yourself (or better prove) that they meet where the initial part of the list enters the loop.
Therefore, the initial part has s - y + t + 1 nodes, and the loop is formed by y nodes, giving s + t + 1 total.
You just want to count the nodes in your linked list right? I've put an example below. But in your case there is a cycle so you also need to detect that in order not to count some of the nodes multiple times.
I've corrected my answer there is now an ordinary count and count in loop (with a fast and slow pointer).
static int count( Node n)
{
int res = 1;
Node temp = n;
while (temp.next != n)
{
res++;
temp = temp.next;
}
return res;
}
static int countInLoop( Node list)
{
Node s_pointer = list, f_pointer = list;
while (s_pointer !=null && f_pointer!=null && f_pointer.next!=null)
{
s_pointer = s_pointer.next;
f_pointer = f_pointer.next.next;
if (s_pointer == f_pointer)
return count(s_pointer);
}
return 0;
}
First find the cycle using Floyd Cycle Detection algorithm and also maintain count when you checking cycle once found loop then print count for the same.
function LinkedList() {
let length = 0;
let head = null;
let Node = function(element) {
this.element = element;
this.next = null;
}
this.head = function() {
return head;
};
this.add = function(element) {
let node = new Node(element);
if(head === null){
head = node;
} else {
let currentNode = head;
while(currentNode.next) {
currentNode = currentNode.next;
}
currentNode.next = node;
}
};
this.detectLoopWithCount = function() {
head.next.next.next.next.next.next.next.next = head; // make cycle
let fastPtr = head;
let slowPtr = head;
let count = 0;
while(slowPtr && fastPtr && fastPtr.next) {
count++;
slowPtr = slowPtr.next;
fastPtr = fastPtr.next.next;
if (slowPtr == fastPtr) {
console.log("\n Bingo :-) Cycle found ..!! \n ");
console.log('Total no. of elements = ', count);
return;
}
}
}
}
let mylist = new LinkedList();
mylist.add('list1');
mylist.add('list2');
mylist.add('list3');
mylist.add('list4');
mylist.add('list5');
mylist.add('list6');
mylist.add('list7');
mylist.add('list8');
mylist.detectLoopWithCount();
There is a "slow" pointer which moves one node at a time. There is a "fast" pointer which moves twice as fast, two nodes at a time.
A visualization as slow and fast pointers move through linked list with 10 nodes:
1: |sf--------|
2: |-s-f------|
3: |--s--f----|
4: |---s---f--|
5: |----s----f|
At this point one of two things are true: 1) the linked list does not loop (checked with fast != null && fast.next != null) or 2) it does loop. Let's continue visualization assuming it does loop:
6: |-f----s---|
7: |---f---s--|
8: |-----f--s-|
9: |-------f-s|
10: s == f
If the linked list is not looped, the fast pointer finishes the race at O(n/2) time; we can remove the constant and call it O(n). If the linked list does loop, the slow pointer moves through the whole linked list and eventually equals the faster pointer at O(n) time.

Find duplicates values in the linked list (Simple convert into difficult)

This seems to be a very simple question. I had been asked by the interviewer to find the duplicate element in the linked list, and then he told me Some of the constraints that made the question difficult for me. the constraint is you have to traverse the linked list only one time.
Resources
The only resource I have available is another linked list.
BONUS
Remove that Element if you can traverse it only one time,
The time should be O(N)
Q1: I can't find the Answer, I don't know if the solution exists or not or he was just confusing me... if yes, how can that be possible?
You could use a HashMap, If this were to be implemented in Java we could use Map datastructure
The Map stores Key-Value Pairs and the elements can be accessed almost O(1), therefore this snippet runs in O(n)
private void removeDuplicates(final Node node) {
Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();
Node n = node;
Node prev = null;
while (n != null) {
if (map.get(n.data) == null) {
map.put(n.data, true);
}
else {
System.out.println("Found Duplicate of: "+n.data);
/*To remove duplicates. do this
if (n.next != null) {
n.data = n.next.data;
n.next = n.next.next;
continue;
}
if (prev != null) {
prev.next = n.next;
}
*/
}
prev = n;
n = n.next;
}
}

Algorithm to check a linked-list for loops

I want to find an algorithm which checks a linked-list with n elements for consistency. The linked-list uses a dummy head (also known as sentinel-node). The algorithm needs to run in O(n) time and is allowed to use O(1) extra space apart from the space needed to iterate through the list. The size of the list is unknown. In addition it's forbidden to modify the list.
A list counts as inconsistent if there is a list item which points at a previous list item.
First I thought about storing the first element and then iterate through the list while comparing the current element with the first one.
Does the list provide a Size property that tells you how many elements it contains (n) without traversing it?
If it does then a simple solution that meets all your big-O requirements is to attempt to traverse the list, counting the elements as you go. If the count exceeds the expected number of elements in the list then it has a loop.
Pseudo-code would look something like this:
bool isConsistent (List list)
{
bool consistent = true;
Node node = list.Sentinel.Next;
int count = 0;
while (node != list.Sentinel && consistent)
{
count++;
if (count > list.Size)
consistent = false;
node = node.Next;
}
return consistent;
}
That completes in O(n) and uses O(1) storage.
Floyd's "Tortoise and Hare" algorithm does what you need and only needs a small modification to work with your dummy head/tail (sentinel) node.
Here is how I would write the pseudo-code:
bool IsConsistent (List list)
{
Node tortoise = list.Sentinel.Next;
Node hare = tortoise.Next;
while (tortoise != list.Sentinel && hare != list.Sentinel)
{
if (tortoise == hare)
return false;
tortoise = tortoise.Next;
hare = hare.Next.Next;
}
return true;
}
You will need some RAM for that if you don't have a Visited property on each item in the linked list.
If you have a Visited property you will first need to clear it before running the algorithm. This will probably not fit your big-O requirements.
It's not clear what you mean with "points at previous list item". Is equal by reference (object) or same value/set of property values (struct)? I assume reference. The code below can easily be modified to handle structs as well.
static void Main(string[] args)
{
var list = BuildALinkedListFromSomeData();
var isConsitent = IsConsistent(list);
}
static bool IsConsistent(LinkedList<Item> list)
{
var visited = new List<LinkedListNode<Item>>()
var runner = list.First;
while(runner != null)
{
if (visited.Contains(runner))
return false;
visited.Add(runner);
runner = runner.Next;
}
return true;
}
A O(n) solution that uses an existing numeric VisitCounter that already uses storage space (no additional storage needed):
static bool IsConsistent(LinkedList<Item> list)
{
var runner = list.First;
if (runner == null)
return false; // Assume consistent if empty
var consistent = true;
var runId = runner.Value.VisitCount;
while (runner != null)
{
// Does the traversed item match the current run id?
if(runner.Value.VisitCount > runId)
{
// No, Flag list as inconsistent. It must have been visited previously during this run
consistent = false;
// Reset the visit count (so that list is ok for next run)
runner.Value.VisitCount = runId;
}
// Increase visit count
runner.Value.VisitCount++;
// Visit next item in list
runner = runner.Next;
}
return consistent;
}
This makes changes to the content of an item in the list, but not the list itself. If you're not allowed to change the content of an item in the list, then of course this is not a solution either. Well, second-thought, this is not a possible solution at all. When inconsistent, your list is circular and the last algorithm will never finish :)
You will then have to traverse the list backwards from each visited item in your list and this will break your O(n+1) requirement.
Conclusion: Not so Mission Impossible if Count is available. See GrahamS' answer
Here is my solution for the second question.
IsConsistent(LinkedList<Item> list) :N
slow = List.Sentinel.next :Element
fast = slow.next :Element
isConsistent = true :boolean
while(fast != list.Sentinel && fast.next != list.Sentinel && isConsistent) do
if(slow == fast)
isConsistent = false
else
slow:= slow.next
fast:= fast.next.next
od
if(isConsistent)
return 0
else
position = 0 : N
slow:= list.Sentinel
while(slow != fast) do
slow:= slow.next
fast:= fast.next
position:= position + 1
od
return position
Basically, pointing a previous item means having a loop inside the list. In this case, loop check is seem appropriate.
At first I didn't thought about using list.Sentinel. Now I got a new idea.
IsConsistent(LinkedList<Item> list) :boolean
slow = List.Sentinel.next :Element
fast = slow.next :Element
while(slow != list.Sentinel && fast != list.Sentinel) do
if(slow == fast) return false
else
slow:= slow.next
fast:= fast.next.next
od
return true

Trying to understand the space complexity of this algorithm

I see a lot of articles online explaining Time complexity but haven't found anything good that explains space complexity well. I was trying to solve the following interview question
You have two numbers represented by a linked list, where each node
contains a single digit. The digits are stored in reverse order, such
that the Ts digit is at the head of the list. Write a function that
adds the two numbers and returns the sum as a linked list.
EXAMPLE
Input: (7-> 1 -> 6) + (5 -> 9 -> 2).That is, 617 + 295.
Output: 2 -> 1 -> 9.That is, 912.
My solution for it is the following:
private Node addLists(Node head1, Node head2) {
Node summationHead = null;
Node summationIterator = null;
int num1 = extractNumber(head1);
int num2 = extractNumber(head2);
int sum = num1 + num2;
StringBuilder strValue = new StringBuilder();
strValue.append(sum);
String value = strValue.reverse().toString();
char[] valueArray = value.toCharArray();
for (char charValue : valueArray) {
Node node = createNode(Character.getNumericValue(charValue));
if (summationHead == null) {
summationHead = node;
summationIterator = summationHead;
} else {
summationIterator.next = node;
summationIterator = node;
}
}
return summationHead;
}
private Node createNode(int value) {
Node node = new Node(value);
node.element = value;
node.next = null;
return node;
}
private int extractNumber(Node head) {
Node iterator = head;
StringBuilder strNum = new StringBuilder();
while (iterator != null) {
int value = iterator.element;
strNum.append(value);
iterator = iterator.next;
}
String reversedString = strNum.reverse().toString();
return Integer.parseInt(reversedString);
}
Can someone please deduce the space complexity for this? Thanks.
The space complexity means "how does the amount of space required to run this algorithm change asymptotically as the inputs get larger"?
So you have two lists of length N and M. The resultant list will have length max(N,M), possibly +1 if there's a carry. But that +1 is a constant, and we don't consider it part of the Big-O as the larger of N or M will dominate.
Also note this algo is pretty straightforward. There's no intermediate calculation requiring larger-than-linear space.
The space complexity is max(N,M).

Resources