MIT6_006S20_ps1-solutions
MIT6_006S20_ps1-solutions
006
Massachusetts Institute of Technology
Instructors: Erik Demaine, Jason Ku, and Justin Solomon Problem Set 1
Problem Set 1
Please write your solutions in the LATEX and Python templates provided. Aim for concise
solutions; convoluted and obtuse descriptions might receive low marks, even when they are
correct.
a) b) c) d)
f1 = log(nn ) f1 = 2n f1 = n n f1 = nn+4 + n!
√
= (log n)n = 6006n
f2 f2
f2 =
n f2 = n 7 n
n
f3 = log(n6006 ) f3 = 26006 n−6 f3 = 43n log n
n
f4 = (log n)6006 f4 = 60062 f3 = (6n)! 2
f4 = 7n
f5 = log log(6006n) f5 = 6006n
2 n
f4 = f5 = n12+1/n
n/6
6
f5 = n
Solution:
a. (f5 , f3 , f4 , f1 , f2 ). Using exponentiation and logarithm rules, we can simplify these to f1 =
Θ(n log n), f2 = Θ((log n)n ), f3 = Θ(log n), f4 = Θ((log n)6006 ), and f5 = Θ(log log n).
For f2 , note that (log n)n > 2n for large n, so this function grows at least exponentially and
is therefore bigger than the rest.
b. (f1 , f2 , f5 , f4 , f3 ). This order follows after converting all the exponent bases to 2. (For exam-
2
ple, f5 = 2log(6006)n .) Remember that asymptotic growth is much more sensitive to changes
in exponents: even if the exponents are both Θ(f (n)) for some function f (n), the functions
will not be the same asymptotically unless their exponents only differ by a constant.
2 Problem Set 1
c. ({f2 , f5 }, f4 , f1 , f3 ). This order follows from the definition of the binomial coefficient and
Stirling’s approximation. f2 has most terms cancel in the numerator and denominator, √ leav-
6 5/6 n
ing a polynomial with leading term n /6!. The trickiest √ one is f4 = Θ((6/(5 )) / n) (by
n
repeated use of Stirling), which is about Θ(1.57 / √n). Thus f4 is bigger than the polynomi-
als but smaller than the factorial and nn . f3 = Θ( n(6n/e)6n ) which grows asymptotically
faster than nn by a factor of Θ(n5n+(1/2) (6/e)6n ). (Originally, f3 was presented as 6n! which
could reasonably be interpretted as 6(n!), which would put f3 before f1 in the order. Because
of this, graders should accept f1 and f3 in either order.)
d. (f5 , f2 , f1 , f3 , f4 ). It is√easiest to see this by taking the logarithms of the functions, which
give us Θ(n log n), Θ( n log n), Θ(n log n), Θ(n2 ), Θ(log n) respectively. However, asymp-
totically similar logarithms do not imply that the functions are asymptotically the same, so
we consider f1 and f3 further. Note that f3 = (4log n )3n = (nlog 4 )3n = n6n . This is bigger
(by about a factor of n5n ) than the larger of f1 ’s terms, so f3 is asymptotically larger.
Rubric:
Problem 1-2. [16 points] Given a data structure D that supports Sequence operations:
where n is the number of items stored in D at the time of the operation, describe algorithms to
implement the following higher-level operations in terms of the provided lower-level operations.
Each operation below should run in O(k log n) time. Recall, delete at returns the deleted item.
(a) reverse(D, i, k): Reverse in D the order of the k items starting at index i (up to
index i + k − 1).
Solution: Thinking recursively, to reverse the k-item subsequence from index i to
index i + k − 1, we can swap the items at index i and index i + k − 1, and then
recursively reverse the rest of the subsequence. As a base case, no work needs to be
done to reverse a subsequence containing fewer than 2 items. This procedure would
then be correct by induction.
It remains to show how to actually swap items at index i and index i + k − 1. Note
that removing an item will shift the index values at all later items. So to keep index
values consistent, we will delete at the later index i + k − 1 first (storing item as
x2 ), and then delete at index i (storing item as x1 ). Then we insert them back in
the opposite order, insert at item x2 at index i, and then insert at item x1 at
index i + k − 1. This swap is correct by the definitions of these operations.
The swapping sub procedure performs four O(log n)-time operations, so occurs in
O(log n) time. Then the recursive reverse procedure makes no more than k/2 = O(k)
recursive calls before reaching a base case, doing one swap per call, so the algorithm
runs in O(k log n) time.
Rubric:
• 5 points for description of algorithm
• 1 point for argument of correctness
• 2 point for argument of running time
• Partial credit may be awarded
4 Problem Set 1
(b) move(D, i, k, j): Move the k items in D starting at index i, in order, to be in front
of the item at index j. Assume that expression i ≤ j < i + k is false.
Solution: Thinking recursively, to move the k-item subsequence starting at i in front
of the item at index j, it suffices to move the item at index i in front of the item B
at index j, and then recursively move the remainder (the (k − 1)-item subsequence
starting at index i in front of . As a base case, no work needs to be done to move a
subsequence containing fewer than 1 item. If we maintain that: i is the index of the
first item to be moved, k is number of items to be moved, and j denotes the index of
the item in front of which we must place items, then this procedure will be correct by
induction.
Note that after removing the item A at index i, if j > i, item B will shift down to be
at index j − 1. Similarly, after inserting A in front of B, item B will be at an index
that is one larger than before, while the next item in the subsequence to be moved will
also be at a larger index if i > j. Maintaining these indices then results in a correct
algorithm.
This recursive procedure makes no more than k = O(k) recursive calls before reach-
ing a base case, doing O(log n) work per call, so the algorithm runs in O(k log n)
time.
Rubric:
• 5 points for description of algorithm
• 1 point for argument of correctness
• 2 point for argument of running time
• Partial credit may be awarded
Problem Set 1 5
We will maintain the separation invariant that P1 , P2 , and P3 are stored contiguously in S with a
non-zero number of empty array slots between them. We also maintain four indices with semantic
6 Problem Set 1
invariants: a1 pointing to the end of P1 , a2 pointing to the start of P2 , b1 pointing to the end of P2 ,
and b2 pointing to the start of P3 .
To support read page(i), there are three cases: either i is the index of a page in P1 , P2 , or P3 .
• If i < n1 , where n1 = |P1 | = ai + 1, then the page is in P1 , and we return page S[i].
• Otherwise, if n1 ≤ i < n1 + n2 , where n2 = |P2 | = (b1 − a2 + 1), then the page is in P2 , and
we return page S[a2 + (i − n1 )].
• Otherwise, i > a1 + n2 , so the page is in P3 , and we return page S[b2 + (i − n1 − n2 )].
This algorithm returns the correct page as long as the invariants on the stored indices are main-
tained, and returns in worst-case O(1) time based on some arithmetic operations and a single array
index lookup.
To support shift mark(m, d), move the relevant page at one of indices (a1 , a2 , b1 , b2 ) to the
index location (a2 − 1, a1 + 1, b2 − 1, b1 + 1) respectively, and then increment the stored indices to
maintain the invariants. This algorithm maintains the invariants of the data structure so is correct,
and runs in O(1) time based on one array index lookup, and one index write. Note that this
operation does not change the amount of extra space between sections P1 , P2 , and P3 , so the
running time of this operation is worst-case.
To support move page(m), move the relavent page at one of indices (a1 , b1 ) to the index location
(b1 + 1, a1 + 1) respectively, and then increment the stored indices to maintain the invariants. If
performing this move breaks the separation invariant (i.e., either pair (a1 , a2 ) or (b1 , b2 ) become
adjacent), rebuild the entire data structure as described above. This algorithm maintains the invari-
ants of the data structure, so is correct. Note that this algorithm: rebuilds any time the extra space
between two adjacent sections closes; after rebuilding, there is n extra space between adjacent
sections; and the extra space between adjacent sections changes by at most one per move page
operation. Thus, since this operation takes O(n) time at most once every n operations, and O(1)
time otherwise, this operation runs in amortized O(1) time.
Rubric:
(a) [8 points] Given a doubly linked list as described above, describe algorithms to im-
plement the following sequence operations, each in O(1) time.
insert first(x) insert last(x) delete first() delete last()
(b) [5 points] Given two nodes x1 and x2 from a doubly linked list L, where x1 occurs
before x2, describe a constant-time algorithm to remove all nodes from x1 to x2 in-
clusive from L, and return them as a new doubly linked list.
Solution: Construct a new empty list L2 in O(1) time, and set its head and tail to be
x1 and x2 respectively. To extract this sub-list, care must be taken when x1 or x2 are
the head or tail of L respectively. If x1 is the head of L, set the new head of L to be
the node a pointed to by x2’s next pointer; otherwise, set the next pointer of the node
pointed to by x1’s previous pointer to a. Similarly, if x2 is the tail of L, set the new tail
of L to be the node b pointed to by x1’s previous pointer; otherwise, set the previous
pointer of the node pointed to by x2’s next pointer to b.
This algorithm removes the nodes from x1 to x2 inclusive directly, so it is correct, and
runs in O(1) time by relinking a constant number of pointers.
Rubric:
• 3 points for description of a correct algorithm
• 1 point for argument of correctness
• 1 point for argument of running time
• Partial credit may be awarded
(c) [6 points] Given node x from a doubly linked list L1 and second doubly linked list L2,
describe a constant-time algorithm to splice list L2 into list L1 after node x. After the
splice operation, L1 should contain all items previously in either list, and L2 should
be empty.
Solution: First, let x1 and x2 be the head and tail nodes of L2 respectively, and let xn be
the node pointed to by the next pointer of x (which may be None). We can remove all
nodes from L2 by setting both it’s head and tail to None. Then to splice in the nodes,
first set the previous pointer of x1 to be x, and set the next pointer of x to be x1.
Similarly, set the next pointer of x2 to be xn. If xn is None, then set x2 to be the new tail
of L; otherwise, set the previous pointer of xn to point back to x2.
This algorithm inserts the nodes from L2 directly into L, so it is correct, and runs in
O(1) time by relinking a constant number of pointers.
Rubric:
• 4 points for description of a correct algorithm
• 1 point for argument of correctness
• 1 point for argument of running time
• Partial credit may be awarded
(d) [25 points] Implement the operations above in the Doubly Linked List Seq
class in the provided code template; do not modify the Doubly Linked List Node
class. You can download the code template including some test cases from the web-
site.
Problem Set 1 9
Solution:
1 class Doubly_Linked_List_Seq:
2 # other template methods omitted
3
4 def insert_first(self, x):
5 new_node = Doubly_Linked_List_Node(x)
6 if self.head is None:
7 self.head = new_node
8 self.tail = new_node
9 else:
10 new_node.next = self.head
11 self.head.prev = new_node
12 self.head = new_node
13
14 def insert_last(self, x):
15 new_node = Doubly_Linked_List_Node(x)
16 if self.tail is None:
17 self.head = new_node
18 self.tail = new_node
19 else:
20 new_node.prev = self.tail
21 self.tail.next = new_node
22 self.tail = new_node
23
24 def delete_first(self):
25 assert self.head
26 x = self.head.item
27 self.head = self.head.next
28 if self.head is None: self.tail = None
29 else: self.head.prev = None
30 return x
31
32 def delete_last(self):
33 assert self.tail
34 x = self.tail.item
35 self.tail = self.tail.prev
36 if self.tail is None: self.head = None
37 else: self.tail.next = None
38 return x
39
40 def remove(self, x1, x2):
41 L2 = Doubly_Linked_List_Seq()
42 L2.head = x1
43 L2.tail = x2
44 if x1 == self.head: self.head = x2.next
45 else: x1.prev.next = x2.next
46 if x2 == self.tail: self.tail = x1.prev
47 else: x2.next.prev = x1.prev
48 x1.prev = None
49 x2.next = None
50 return L2
51
52 def splice(self, x, L2):
53 xn = x.next
54 x1 = L2.head
55 x2 = L2.tail
56 L2.head = None
57 L2.tail = None
58 x1.prev = x
59 x.next = x1
60 x2.next = xn
61 if xn: xn.prev = x2
62 else: self.tail = x2
MIT OpenCourseWare
https://ocw.mit.edu
For information about citing these materials or our Terms of Use, visit: https://ocw.mit.edu/terms