---
# System prepended metadata

title: 'SE103, Week 1 HackerRank Solutions'

---

# SE103, Week 1 HackerRank Solutions

### Multiple Choice Question 1: Minimum Bytes Per Node
Question: On a 64-bit machine, what is the minimum number of bytes per node needed to implement a Singly Linked List, assuming that each node stores a reference to its value?

**Answer**: 16

### Multiple Choice Question 2: List Operations
Question: Given the list `1->2`, what would the result look like after the following operations are applied sequentially?

 

 Insert(3)
 Insert(4)
 Delete(1)
 

What about after setting `head.next.next.val = 5`?

**Answer**: 4->3->2 4->3->5

### Multiple Choice Question 3: Time and Space Complexity
What is the space and time complexity of the following algorithm for reversing a linked list?

```python= 
def get_last(head):
    if not head or not head.next:
        return head
    return get_last(head.next)

def reverse(head):
    if not head or not head.next:
        return head
    r = reverse(head.next)
    l = get_last(r)
    head.next = None
    l.next = head
    return r
```

**Answer**: Time Complexity: O(n^2) Space Complexity: O(n^2)

### Multiple Choice Question 4: Execution by Hand
What is the output of running the following code with the input `head = 1 → 2 → 3 → 4 → 5, k = 3`?

```python=
def do_what(head, k):
    if not head:
        return head

    e = head
    ne = head
    i = 0
    while i < k:
        e = e.next
        if not e:
            return head
        i += 1

    while e.next:
        ne = ne.next
        e = e.next

    d = Node("d")
    d.next = ne.next
    ne.next = None
    e.next = head
    return d.next
```

**Answer**: 3->4->5->1->2

### Multiple Choice Question 5: Algorithm Space Complexity
What is the space complexity of the following algorithm for splitting a linked list into parts?

```python=
def splitListToParts(root, k):
    if k < 2:
        return [root]

    len_l = 0
    c = root
    while c:
        len_l += 1
        c = c.next
    binlen = int(len_l / k)
    olen = len_l - binlen * k
    blens = [binlen for i in range(k)]
    for i in range(olen):
        blens[i] += 1

    ds = [ListNode("dummy") for _ in range(k)]
    c = root
    t = 0
    b = 0
    cd = ds[0]
    while c:
        if t == blens[b]:
            b += 1
            t = 0
            cd = ds[b]
        cd.next = c
        c = c.next
        cd = cd.next
        cd.next = None
        t += 1
    return [d.next for d in ds]
```

**Answer**: O(k)

### Coding Question 1: Compute Length
Compute the length of the list A.


```java=
static int getLength(SinglyLinkedListNode A) {
    int length = 0;
    while (A != null) {
        A = A.next;
        length += 1;
    }
    return length;
}
```

```python=
# Iterate over the list until there are no more elements, incrementing a
# counter at each iteration.
def getLength(A):
    length = 0
    while A:
        A = A.next
        length += 1
    return length
```

### Coding Question 2: Palindrome Linked List
Given a singly linked list, determine if it is a palindrome.

```java=
class Solution {
    static SinglyLinkedListNode reverseList(SinglyLinkedListNode head) {
        if (head == null || head.next == null)
            return head;
        SinglyLinkedListNode cur = head;
        SinglyLinkedListNode next = head.next;
        cur.next = null;
        while (next != null) {
            SinglyLinkedListNode temp = next.next;
            next.next = cur;
            cur = next;
            next = temp;
        }
        return cur;
    }
    static int len(SinglyLinkedListNode A) {
        int length = 0;
        while (A != null) {
            A = A.next;
            length++;
        }
        return length;
    }
    static boolean isPalindrome(SinglyLinkedListNode A) {
        int length = len(A);
        if (length == 0 || length == 1) return true;
        int half = (length + 1) / 2;
        SinglyLinkedListNode cur = A;
        for (int i = 0; i < half; i++)
            cur = cur.next;
        SinglyLinkedListNode secondHalfReversed = reverseList(cur);
        cur = secondHalfReversed;
        while (cur != null && A != null) {
            if (cur.data != A.data)
                return false;
            cur = cur.next;
            A = A.next;
        }
        return true;
    }
};
```

```python=
# The key insight is that we want to iterate from both ends of the list
# simultaneously to see whether they match, but a singly linked list only
# supports fast iteration in one direction. Therefore, we need to reverse the
# right half of the list.
# Thus, the algorithm first cuts the list in half and reverses the second half.
# Then, iterate over the two halves together, and return False if one of their
# values does not match. If all values match, then the original list was a
# palindrome. Finding the halfway point of the list, reversing the list, and
# iterating over the two halves each take linear time, so the algorithm takes
# linear time overall.
def isPalindrome(A):
    def getLength(A):
        length = 0
        while A:
            A = A.next
            length += 1
        return length

    def reverseList(head):
        previous = None
        while head:
            next_head = head.next
            head.next = previous
            previous = head
            head = next_head
        return previous

    length = getLength(A)
    if length == 0 or length == 1:
        return True
    half = (length + 1) / 2
    cur = A
    for i in xrange(half):
        cur = cur.next
    secondHalfReversed = reverseList(cur)
    cur = secondHalfReversed
    while cur and A:
        if cur.data != A.data:
            return False
        cur = cur.next
        A = A.next
    return True
```

### Coding Question 3: Plus One Linked List
Given a non-negative integer represented as a non-empty singly linked list of digits, add one to the integer.

You may assume the integer do not contain any leading zero, except the number 0 itself.

The digits are stored such that the most significant digit is at the head of the list.

```java=
class Solution {
    static SinglyLinkedListNode reverseList(SinglyLinkedListNode head) {
        if (head == null || head.next == null)
            return head;
        SinglyLinkedListNode cur = head;
        SinglyLinkedListNode next = head.next;
        cur.next = null;
        while (next != null) {
            SinglyLinkedListNode temp = next.next;
            next.next = cur;
            cur = next;
            next = temp;
        }
        return cur;
    }
    static SinglyLinkedListNode addOne(SinglyLinkedListNode A) {
        A = reverseList(A);
        SinglyLinkedListNode cur = A;
        cur.data++;
        while (cur.data == 10) {
            cur.data = 0;
            if (cur.next != null) {
                cur = cur.next;
                cur.data++;
            } else {
                cur.next = new SinglyLinkedListNode(1);
                break;
            }
        }
        return reverseList(A);
    }
}
```

```python=
# The key insight is that adding one to a number represented as a linked list
# may result in the need to propagate an increment from the least sigificant
# digit upwards to the most significant digit. Since it is expensive to iterate
# a linked list backwards, the algorithm below first reverses the linked list.
# It then increments the least sigificant digit and performs any carrying
# operation that is needed, and returns the revese of the resulting list. The
# two linked list reversals and the propagation each take linear time, so the
# overall running time is O(n).
def addOne(A):
    def reverseList(head):
        previous = None
        while head:
            next_head = head.next
            head.next = previous
            previous = head
            head = next_head
        return previous

    A = reverseList(A)
    cur = A
    cur.data += 1
    while cur.data == 10:
        cur.data = 0
        if cur.next:
            cur = cur.next
            cur.data += 1
        else:
            cur.next = SinglyLinkedListNode(1)
            break
    return reverseList(A)
```

### Coding Question 4: LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

 

An optimal can do both operations in O(1) time complexity.

 

Feel free to implement or use any data structures available in the standard library, unless you find a pre-built LRU Cache in the standard library.

 

Here is an example usage.

```java=
LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4
```

**Java**
```java=
import java.util.*;

static class LRUCache {
    class Node{
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        public int key;
        public int value;
        public Node prev;
        public Node next;
    }

    int capacity;
    Node evictionQueueHead;
    Node evictionQueueTail;
    Map<Integer, Node> nodes;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        nodes = new HashMap<Integer, Node>();
    }

    private void evict(Node node) {
        if (node.prev != null) {
            node.prev.next = node.next;
        }
        if (node.next != null) {
            node.next.prev = node.prev;
        }
        if (node == evictionQueueTail) {
            evictionQueueTail = evictionQueueTail.prev;
        }
        if (node == evictionQueueHead) {
            evictionQueueHead = evictionQueueHead.next;
        }
    }

    private void addToEvictionQueue(Node node) {
        if (evictionQueueTail == null) {
            evictionQueueTail = evictionQueueHead = node;
            return;
        }
        evictionQueueTail.next = node;
        node.prev = evictionQueueTail;
        node.next = null;
        evictionQueueTail = evictionQueueTail.next;
    }

    public int get(int key) {
        if (!nodes.containsKey(key)) {
            return -1; 
        }
        Node node = nodes.get(key);
        evict(node);
        addToEvictionQueue(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (!nodes.containsKey(key) && nodes.size() >= capacity) {
            // Need to evict head of queue first.
            nodes.remove(evictionQueueHead.key);
            evict(evictionQueueHead);
        }

        Node node = null;
        if (nodes.containsKey(key)) {
            evict(nodes.get(key));
            node = nodes.get(key);
            node.value = value;
        } else {
            node = new Node(key, value);
            nodes.put(key, node);
        }
        addToEvictionQueue(node);
    }
}
```

**Python**
```python=
# The LRU cache implementation below represents the user's data as a doubly
# linked list and a map from keys to nodes in the list.  Each node contains a
# key-value pair, and nodes are stored from least recently used to most
# recently used. Lookups for both get() and put() use the map to find the
# target node, and lookups cause the node that was accessed to move to the end
# of the linked list. If a put() occurs on a new key and the queue is full, the
# node at the beginning of the list is evicted by removing its key from the map
# and removing it from the list.
# Removing an element from a doubly linked list is O(1).
# Adding an element to the end of a doubly linked list when one maintains a
# tail pointer is O(1).
# Looking up a key in a map is O(1).
# These are the only operations used by this implementation, so it supports
# both get and put in O(1) time,
class LRUCache(object):
    # This class represents a node in a doubly linked list which is used as an
    # eviction queue for the LRU Cache.
    class Node:
        def __init__(self, key, value):
            self.key = key
            self.value = value
            self.prev = None
            self.next = None

    def __init__(self, capacity):
        # Store the capacity so that we will know when we must evict.
        self.capacity = capacity

        self.evictionQueueHead = None
        self.evictionQueueTail = None

        # Map keys to nodes in the doubly linked list. The nodes store the
        # actual values.
        self.nodes = {}

    # Remove an element from the eviction queue.
    def evict(self, node):
        if node.prev:
            node.prev.next = node.next
        if node.next:
            node.next.prev = node.prev
        if node == self.evictionQueueTail:
            self.evictionQueueTail = self.evictionQueueTail.prev
        if node == self.evictionQueueHead:
            self.evictionQueueHead = self.evictionQueueHead.next

    # Add an element to the end of the eviction queue. This method assumes that
    # the node being added is not already in the eviction queue
    def addToEvictionQueue(self, node):
        if self.evictionQueueTail == None:
            self.evictionQueueTail = self.evictionQueueHead = node
            return
        self.evictionQueueTail.next = node
        node.prev = self.evictionQueueTail
        # This node may have been attached elsewhere before.
        node.next = None
        self.evictionQueueTail = self.evictionQueueTail.next

    def get(self, key):
        """
        :type key: int
        :rtype: int
        """
        if key not in self.nodes:
            return -1

        node = self.nodes[key]
        self.evict(node)
        self.addToEvictionQueue(node)
        return node.value

    def put(self, key, value):
        """
        :type key: int
        :type value: int
        :rtype: void
        """
        if key not in self.nodes and len(self.nodes) >= self.capacity:
            # Need to evict head of queue first.
            del self.nodes[self.evictionQueueHead.key]
            self.evict(self.evictionQueueHead)

        node = None
        if key in self.nodes:
            self.evict(self.nodes[key])
            node = self.nodes[key]
            node.value = value
        else:
            node = self.Node(key, value)
            self.nodes[key] = node
        self.addToEvictionQueue(node)
```