|
| 1 | +Daily Coding Problem #6 |
| 2 | +Problem |
| 3 | +This problem was asked by Google. |
| 4 | + |
| 5 | +An XOR linked list is a more memory efficient doubly linked list. Instead of each node holding next and prev fields, it holds a field named both, which is an XOR of the next node and the previous node. Implement an XOR linked list; it has an add(element) which adds the element to the end, and a get(index) which returns the node at index. |
| 6 | + |
| 7 | +If using a language that has no pointers (such as Python), you can assume you have access to get_pointer and dereference_pointer functions that converts between nodes and memory addresses. |
| 8 | + |
| 9 | +Solution |
| 10 | +For the head, both will just be the address of next, and if it's the tail, it should just be the address of prev. And intermediate nodes should have an XOR of next and prev. |
| 11 | + |
| 12 | +Here's an example XOR linked list which meets the above conditions: |
| 13 | + |
| 14 | +A <-> B <-> C <-> D |
| 15 | + |
| 16 | +B A ⊕ C B ⊕ D C |
| 17 | +Let's work through get first, assuming that the above conditions are maintained. Then, given a node, to go to the next node, we have to XOR the current node's both with the previous node's address. And to handle getting the next node from the head, we would initialize the previous node's address as 0. |
| 18 | + |
| 19 | +So in the above example, A's both is B which when XOR'd with 0 would become B. Then B's both is A ⊕ C, which when XOR'd with A becomes C, etc. |
| 20 | + |
| 21 | +To implement add, we would need to update current tail's both to be XOR'd by its current both the new node's memory address. Then the new node's both would just point to the memory address of the current tail. Finally, we'd update the current tail to be equal to the new node. |
| 22 | + |
| 23 | +import ctypes |
| 24 | + |
| 25 | + |
| 26 | +# This is hacky. It's a data structure for C, not python. |
| 27 | +class Node(object): |
| 28 | + def __init__(self, val): |
| 29 | + self.val = val |
| 30 | + self.both = 0 |
| 31 | + |
| 32 | + |
| 33 | +class XorLinkedList(object): |
| 34 | + def __init__(self): |
| 35 | + self.head = self.tail = None |
| 36 | + self.__nodes = [] # This is to prevent garbage collection |
| 37 | + |
| 38 | + def add(self, node): |
| 39 | + if self.head is None: |
| 40 | + self.head = self.tail = node |
| 41 | + else: |
| 42 | + self.tail.both = id(node) ^ self.tail.both |
| 43 | + node.both = id(self.tail) |
| 44 | + self.tail = node |
| 45 | + |
| 46 | + # Without this line, Python thinks there is no way to reach nodes between |
| 47 | + # head and tail. |
| 48 | + self.__nodes.append(node) |
| 49 | + |
| 50 | + |
| 51 | + def get(self, index): |
| 52 | + prev_id = 0 |
| 53 | + node = self.head |
| 54 | + for i in range(index): |
| 55 | + next_id = prev_id ^ node.both |
| 56 | + |
| 57 | + if next_id: |
| 58 | + prev_id = id(node) |
| 59 | + node = _get_obj(next_id) |
| 60 | + else: |
| 61 | + raise IndexError('Linked list index out of range') |
| 62 | + return node |
| 63 | + |
| 64 | + |
| 65 | +def _get_obj(id): |
| 66 | + return ctypes.cast(id, ctypes.py_object).value |
| 67 | +add runs in O(1) time and get runs in O(N) time. |
0 commit comments