# SE103 Week 1 Post Session Solutions [Add Two Numbers](#Add-two-numbers-MSF) [Copy List With Random Pointer](#Copy-List-With-Random-Pointer) [Intersection of Two Lists](#Intersection-of-Two-Lists) [Merge K Sorted Lists](#Merge-K-Sorted-Lists) [Merge Two Sorted Lists](#Merge-Two-Sorted-Lists) [Remove Duplicates II](#Remove-Duplicates-II) [Reorder List](#Reorder-List) ## Add two numbers (MSF) **Java** ```java class Solution { public ListNode reverseList(ListNode head) { ListNode previous = null; while (head != null) { ListNode next_head = head.next; head.next = previous; previous = head; head = next_head; } return previous; } public ListNode addTwoNumbersLSF(ListNode l1, ListNode l2) { ListNode resultList = new ListNode(0); // Create a variable for walking the resulting list ListNode cur = resultList; // Track the previous node so that we can remove the current node if it // turned out to be unnecessary. ListNode prev = null; while (l1 != null || l2 != null) { if (l1 != null) { cur.val += l1.val; l1 = l1.next; } if (l2 != null) { cur.val += l2.val; l2 = l2.next; } // Create the next node with a value of 1 iff there is a digit // carried from this iteration; otherwise it has a value of 0. cur.next = new ListNode(cur.val / 10); cur.val %= 10; prev = cur; cur = cur.next; } // If the most recently added node has value 0, then it is a leading // zero and can be truncated. if (cur.val == 0) { prev.next = null; } return resultList; } public ListNode addTwoNumbers(ListNode l1, ListNode l2) { l1 = reverseList(l1); l2 = reverseList(l2); ListNode result = addTwoNumbersLSF(l1, l2); return reverseList(result); } } ``` **Python** ```Python # Reverse both linked lists, then use the same algorithm as the variation of # this problem with the least significant digit first. Finally, reverse the # result to get the solution in most signficiant digit first form and return # it. Since reversing a linked list is linear time and the original algorithm # was linear time, the running time of this algorithm is also linear time. class Solution(object): def reverseList(self, head): previous = None while head: next_head = head.next head.next = previous previous = head head = next_head return previous def addTwoNumbersLSF(self, l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ resultList = ListNode(0) # Create a variable for walking the resulting list cur = resultList # Track the previous node so that we can remove the current node if it # turned out to be unnecessary. prev = None while l1 or l2: if l1: cur.val += l1.val l1 = l1.next if l2: cur.val += l2.val l2 = l2.next # Create the next node with a value of 1 iff there is a digit # carried from this iteration; otherwise it has a value of 0. cur.next = ListNode(cur.val / 10) cur.val %= 10 prev = cur cur = cur.next # If the most recently added node has value 0, then it is a leading # zero and can be truncated. if cur.val == 0: prev.next = None return resultList def addTwoNumbers(self, l1, l2): l1 = self.reverseList(l1) l2 = self.reverseList(l2) result = self.addTwoNumbersLSF(l1, l2) return self.reverseList(result) ``` ## Copy List With Random Pointer **Python** ```python # The primary challenge in this problem is finding the node in the copied list # corresponding to a particular node in the original list (to assign the proper # value for the random pointer). # # One solution, which uses O(n) storage, is to create a hashtable which maps # each node in the original list to a node in the copy of the list. Then, one # could copy the list in a single pass, and correct the random pointers in a # second pass by looking up the corresponding node in the hashtable. # # Another solution, which takes O(n^2) time, is to do a linear search through # the original list to find the position of the random node and iterate to the # corresponding position in the copied list. # # The solution below arranges the copied nodes to initially live immediately # after their corresponding original node. This makes it easy to find all the # random pointers in constant time without extra storage. After the random # pointers have been assigned, the list is peeled apart like a zipper; all the # odd nodes return to the original list and all the even nodes form the copied # list. class Solution(object): def copyRandomList(self, head): """ :type head: RandomListNode :rtype: RandomListNode """ if not head: return None # Step 1: Duplicate each node and put it after the existing node. cur = head while cur: originalNext = cur.next cur.next = RandomListNode(cur.label) cur.next.next = originalNext cur = originalNext # Step 2: Copy all the random pointers cur = head while cur: copyCur = cur.next if cur.random: copyCur.random = cur.random.next cur = cur.next.next # Step 3: Peel the list apart, and return the new copy cur = head copyHead = head.next copyCur = copyHead # Remainder of the combined list combined = head.next.next isCopy = False while combined: if not isCopy: cur.next = combined cur = cur.next else: copyCur.next = combined copyCur = copyCur.next isCopy = not isCopy combined = combined.next cur.next = None return copyHead ``` **Java** ```java public class Solution { public RandomListNode copyRandomList(RandomListNode head) { if (head == null) return null; // Step 1: Duplicate each node and put it after the existing node. RandomListNode cur = head; while (cur != null) { RandomListNode originalNext = cur.next; cur.next = new RandomListNode(cur.label); cur.next.next = originalNext; cur = originalNext; } // Step 2: Copy all the random pointers cur = head; while (cur != null) { RandomListNode copyCur = cur.next; if (cur.random != null) copyCur.random = cur.random.next; cur = cur.next.next; } // Step 3: Peel the list apart, and return the new copy cur = head; RandomListNode copyHead = head.next; RandomListNode copyCur = copyHead; // Remainder of the combined list RandomListNode combined = head.next.next; boolean isCopy = false; while (combined != null) { if (!isCopy) { cur.next = combined; cur = cur.next; } else { copyCur.next = combined; copyCur = copyCur.next; } isCopy = !isCopy; combined = combined.next; } cur.next = null; return copyHead; } } ``` ## Intersection of Two Lists **Python** ```python # If two lists intersect and have different lengths, then the difference must # show up in the non-intersecting parts of the list. Therefore, iterate through # the longer list until the two lists are the same length, and then iterate the # lists together to find the intersecting node if it exists. # Computing the length of a linked list is a linear time operation. Iterating # over the two lists together is also a linear time operation. Therefore, the # overall running time is O(n). class Solution(object): def getLen(self, headA): s = 0 while headA: headA = headA.next s+=1 return s def getIntersectionNode(self, headA, headB): """ :type head1, head1: ListNode :rtype: ListNode """ Alen = self.getLen(headA) Blen = self.getLen(headB) if Alen < Blen: delta = Blen - Alen # Advance B's head until the two lists are the same length. for _ in xrange(delta): headB = headB.next else: delta = Alen - Blen # Advance A's head until the two lists are the same length. for _ in xrange(delta): headA = headA.next while headA: if headA == headB: return headA headA = headA.next headB = headB.next return None ``` **Java** ```java public class Solution { public int getLen(ListNode head) { int count = 0; while (head != null) { head = head.next; count++; } return count; } public ListNode getIntersectionNode(ListNode headA, ListNode headB) { int Alen = getLen(headA); int Blen = getLen(headB); if (Alen < Blen) { int delta = Blen - Alen; // Advance B's head until the two lists are the same length. for (int i = 0; i < delta; i++) headB = headB.next; } else { int delta = Alen - Blen; // Advance A's head until the two lists are the same length. for (int i = 0; i < delta; i++) headA = headA.next; } while (headA != null) { if (headA == headB) return headA; headA = headA.next; headB = headB.next; } return null; } } ``` ## Merge K Sorted Lists **Python** ```python # In the standard algorithm for merging two sorted linked lists, each step of # the algorithm compares the head nodes of each list, and removes the smaller # head, adding it to the merged list. # A naive solution to this problem uses a linear search to find the # minimum of the k heads at every iteration, but the running time would be # O(kn), where k is the number of linked lists and n is the sum of the length # of each linked list. # The solution below holds the smallest node in each linked list in a heap, a # data structure in which both extraction of the smallest element and insertion # of any element is O(log k). The heap is initialized with the smallest # element from each list. At each iteration, the smallest element from the # heap is added to the output linked list, and its child node (if not null) is # added into the heap. The algorithm iterates until all the origial lists are # exhausted. Since each iteration takes 2 operations which take O(log k) time # and there are n iterations, the overall running time is O(n log k). import heapq # Python's heapq interface is a little verbose and doesn't support keys; this # class is a thin wrapper that makes it slightly easier to deal with. class Heap: def __init__(self, key=lambda x: x): self.storage = [] self.key = key # Discard attempts to push None. def push(self, item): if item: heapq.heappush(self.storage, (self.key(item), item)) def pop(self): try: _, item = heapq.heappop(self.storage) return item except: return None def length(self): return len(self.storage) class Solution(object): def mergeKLists(self, lists): """ :type lists: List[ListNode] :rtype: ListNode """ # Initialize: Add the smallest node from each list into the heap. minElements = Heap(key=lambda x : x.val) for i in xrange(len(lists)): if lists[i]: minElements.push(lists[i]) lists[i] = lists[i].next # Use the dummy head node to avoid handling the first element # differently than the remaining elements. dummyHead = ListNode("dummy") parent = dummyHead # Take the smallest element in the heap, and push its child # onto the heap. while minElements.length() != 0: cur = minElements.pop() minElements.push(cur.next) parent.next = cur parent = parent.next return dummyHead.next ``` **Java** ```java class Solution { static class ListNodeComparator implements Comparator<ListNode> { public int compare(ListNode a, ListNode b) { return a.val - b.val; } } public ListNode mergeKLists(ListNode[] lists) { // Initialize: Add the smallest node from each list into the heap. // We use lists.length + 1 because Java PriorityQueue does not allow // initial capacity of 0. PriorityQueue<ListNode> minElements = new PriorityQueue(lists.length + 1, new ListNodeComparator()); for (int i = 0; i < lists.length; i++) { if (lists[i] != null) { minElements.offer(lists[i]); lists[i] = lists[i].next; } } // Use the dummy head node to avoid handling the first element // differently than the remaining elements. ListNode dummyHead = new ListNode(-1); ListNode parent = dummyHead; // Take the smallest element in the heap, and push its child // onto the heap. while (minElements.size() != 0) { ListNode cur = minElements.poll(); if (cur.next != null) minElements.offer(cur.next); parent.next = cur; parent = parent.next; } return dummyHead.next; } } ``` ## Merge Two Sorted Lists **Python** ```python # As long as both lists have elements, repeatedly compare their smallest # elements and move the smaller element to the merged list. When one list is # exhausted, append the remaining chunk of the remaining list to the merged # list. # This algorithm consumes one node from one of the lists at every iteration, # and performs a constant amount of work to do so. Therefore, the running time # is O(m + n), where m and n are the lengths of the lists. class Solution(object): def mergeTwoLists(self, l1, l2): """ :type l1: ListNode :type l2: ListNode :rtype: ListNode """ dummyHead = ListNode("dummy") cur = dummyHead while l1 and l2: if l1.val < l2.val: cur.next = l1 l1 = l1.next else: cur.next = l2 l2 = l2.next cur = cur.next if not l1: cur.next = l2 else: cur.next = l1 return dummyHead.next ``` **Java** ```java class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { // 0 is an arbitrary value here ListNode dummyHead = new ListNode(0); ListNode cur = dummyHead; while (l1 != null && l2 != null) { if (l1.val < l2.val) { cur.next = l1; l1 = l1.next; } else { cur.next = l2; l2 = l2.next; } cur = cur.next; } if (l1 == null) cur.next = l2; else cur.next = l1; return dummyHead.next; } } ``` ## Remove Duplicates II **Python** ```python # The high level idea is to start at some non-duplicate parent node and # examine its child to determine whether the child is a duplicate. If the child # is a duplicate, then remove the child and all of its duplicates by pointing # the parent's next pointer at the first node with a value that does not match # the original child's value. If the child is not a duplicate, then the child # becomes the new parent. In order to guarantee that the first parent is not a # duplicate, we create a dummy head with a value not equal to the original head # and use that as the initial parent. # Since this algorithm either advances the parent pointer or eliminates at # least one child in each iteration, and each iteration takes a constant amount # of work, it completes in O(n) time. class Solution(object): def deleteDuplicates(self, head): """ :type head: ListNode :rtype: ListNode """ # Attach a dummy head so that duplicate entries at the start don't need # to be a special case. dummyHead = ListNode("dummy") dummyHead.next = head parent = dummyHead current = parent.next while parent and current: # No duplicate, advance both pointers. if not current.next or current.next.val != current.val: parent = current current = current.next continue # There is a duplicate, advance the second pointer until it points # to a node with a value different from its previous value. val = current.val while current and current.val == val: current = current.next parent.next = current return dummyHead.next ``` **Java** ```java class Solution { public ListNode deleteDuplicates(ListNode head) { // Attach a dummy head so that duplicate entries at the start don't need // to be a special case. ListNode dummyHead = new ListNode(-1); dummyHead.next = head; ListNode parent = dummyHead; ListNode current = parent.next; while (parent != null && current != null) { // No duplicate, advance both pointers. if (current.next == null || current.next.val != current.val) { parent = current; current = current.next; continue; } // There is a duplicate, advance the second pointer until it points // to a node with a value different from its previous value. int val = current.val; while (current != null && current.val == val) { current = current.next; } parent.next = current; } return dummyHead.next; } } ``` ## Reorder List **Python** ```python # The key challenge in writing an efficient solution is figuring out how to # iterate from both ends of the list at the same time. # The key insight is to chop the list at the halfway point, and reverse the # second half of the linked list. Then, one can iterate both lists # simultaneously and zip them together to achieve the desired reordering. # Since linked list reversal and traversal are both linear time, the overall # algorithm is also O(n). class Solution(object): def reverseList(self, head): previous = None while head: next_head = head.next head.next = previous previous = head head = next_head return previous def getLen(self, head): s = 0 while head: head = head.next s+=1 return s def reorderList(self, head): """ :type head: ListNode :rtype: void Do not return anything, modify head in-place instead. """ # Split the list in half and reverse the second half halfLength = (self.getLen(head) + 1) / 2 secondHalf = head parent = None for _ in xrange(halfLength): parent = secondHalf secondHalf = secondHalf.next secondHalf = self.reverseList(secondHalf) # Truncate the first half of the list. if parent: parent.next = None # Zip the two pieces together while head and secondHalf: nextHead = head.next nextSecond = secondHalf.next head.next = secondHalf secondHalf.next = nextHead head = nextHead secondHalf = nextSecond ``` **Java** ```java class Solution { private ListNode reverseList(ListNode head) { ListNode previous = null; while (head != null) { ListNode next_head = head.next; head.next = previous; previous = head; head = next_head; } return previous; } private int getLen(ListNode head) { int count = 0; while (head != null) { head = head.next; count++; } return count; } public void reorderList(ListNode head) { // Split the list in half and reverse the second half int halfLength = (getLen(head) + 1) / 2; ListNode secondHalf = head; ListNode parent = null; for (int i = 0; i < halfLength; i++) { parent = secondHalf; secondHalf = secondHalf.next; } secondHalf = reverseList(secondHalf); // Truncate the first half of the list. if (parent != null) parent.next = null; // Zip the two pieces together while (head != null && secondHalf != null) { ListNode nextHead = head.next; ListNode nextSecond = secondHalf.next; head.next = secondHalf; secondHalf.next = nextHead; head = nextHead; secondHalf = nextSecond; } } } ```