0% found this document useful (0 votes)
62 views5 pages

CS 3870: Exercise Solutions: Week 1, Part 2

This document contains solutions to exercises from CS 3870 on October 7, 2020. Week 1 solutions include using binary search in O(log n) time to find a poisoned bottle among n bottles, and analyzing an O(n^2) dynamic programming algorithm to find the maximum subarray sum due to a bug. Week 2 solutions analyze recurrences using the master theorem and find an O(n) algorithm for maximum subarray sum called Kadane's algorithm.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
62 views5 pages

CS 3870: Exercise Solutions: Week 1, Part 2

This document contains solutions to exercises from CS 3870 on October 7, 2020. Week 1 solutions include using binary search in O(log n) time to find a poisoned bottle among n bottles, and analyzing an O(n^2) dynamic programming algorithm to find the maximum subarray sum due to a bug. Week 2 solutions analyze recurrences using the master theorem and find an O(n) algorithm for maximum subarray sum called Kadane's algorithm.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 5

CS 3870: Exercise solutions

October 7, 2020

Week 1, part 2
4. Let’s say you have n wine bottles, one of which is filled with poison, but the rest are
filled with fine wine (or, if you prefer, you may think of this as having n blood samples
and a very precise test for coronavirus). You have a test that can detect the presence
of poison, but the test takes a lot of time to perform. How many tests do you need to
perform to find the poisoned bottle? Note: Your test is precise enough that it can detect
even small quantities of poison in a mix, i.e., you’re allowed to mix the contents of several
bottles in a single test.
Describe your strategy. An asymptotic bound on the number of tests as a function of
n is enough (i.e., in O(. . .) notation).

ANSWER: This is effectively binary search. Split the bottles into two halves,
mix a sample from every bottle in the first half into a test container, and test
for poison. If there is poison, put the second set of bottles aside and split the
first half. If there is no poison, put the first set of bottles aside and split the
second half. Repeat until only one bottle remains.
The number of tests required is O(log n).

5 (potentially trickier). Consider the following (pseudocode) implementation of Find-


MaxSubarray and FindBestCrossing. Assume as above that all division is integer division
(i.e., 7/2=floor(7/2)=3).

def FindBestCrossing(array, midpoint):


left_sum = 0
best_i = midpoint
best_left_sum = 0
for i from midpoint-1 down to 0:
left_sum = left_sum + array[i]
if left_sum > best_left_sum:
best_i = i
best_left_sum = left_sum
right_sum = 0
best_j = midpoint
best_right_sum = 0

1
for j from midpoint+1 to len(array)-1:
right_sum = right_sum + array[j]
if right_sum > best_right_sum:
best_j = j
best_right_sum = right_sum
return best_left_sum + array[midpoint] = best_right_sum

def FindMaxSubarray(array, low, high):


if low >= high:
return array[low]
midpoint = (low + high)/2
return max(FindMaxSubarray(array, low, midpoint-1),
FindBestCrossing(array, midpoint),
FindMaxSubarray(array, midpoint+1, high))

def MaxSubarray(array):
return FindMaxSubarray(array, 0, len(array)-1)

There is an issue with the implementation that significantly affects the running time.

(a) What is this implementation issue and why does it make a difference?

(b) Compute the asymptotic running time of the above implementation if len(array)=n,
in O(...) or theta notation. HINT: First figure out the exact size of the recursion
tree, then use this insight and the issue from 5(a) to compute the resulting running
time.

ANSWER: Indeed, this question was tricky, as the problem is precisely the
kind of issue that easily slips between the cracks. The issue is the following:

• The function FindBestCrossing(array, midpoint) always scans the


entirety of the argument array that it is given, which takes Θ(len(array))
time
• But it is called from FindMaxSubarray(array, low, high), where it is
only supposed to scan the slice array[low...high], which is allowed to
take O(high − low + 1) time only

That is, the call to FindBestCrossing does not extract a slice of the array,
nor does it pass through the arguments low and high.
This means that the running time is not appropriately modelled by a recur-
rence (since the amount of local work in a call is not determined by the current
value of n = high − low + 1).
For an analysis, consider the following.

1. The recursion tree contains precisely len(array) nodes. Specifically, for


every index i = 0, . . . , n − 1, we have midpoint=i in precisely one node.

2
2. Approximately half of these are not leaf nodes, i.e., the tree has Θ(len(array))
non-leaf nodes
3. In every non-leaf node, the call to FindBestCrossing takes Θ(len(array))
time.

So if n =len(array) is the length of the array sent to the MaxSubarray function,


the total running time is Θ(n2 ).

Week 2
1, from CLRS Problem 4-1. Give the asymptotic growth of the following recurrence
relations, for example using the Master theorem.

(a) T (n) = 2T (n/2) + n4

(b) T (n) = 16T (n/4) + n2

(c) T (n) = 7T (n/3) + n2



(d) T (n) = 2T (n/4) + n

ANSWER:

(a) Θ(n4 ) since 4 > log2 2 = 1 (equivalently, since 2 < 24 )


(b) Θ(n2 log n) since 2 = log4 16 = (log 16)/(log 4) = 4/2 (equivalently, since
16 = 42 )
(c) Θ(n2 ) since 2 > log3 7 (note that 2 = log3 32 = log3 9 > log3 7) (equiva-
lently, since 7 < 32 )
√ √
(d) Θ( n log n) since 1/2 = log4 2 (note that n = n1/2 ) (equivalently, since
(1/2) = 4(1/2) )

2. Consider the recursive algorithm cantor, given below.

(a) Draw the tree of all recursive calls for cantor(0,15)

(b) Write a recurrence relation modelling the running time of a call cantor(1,n)

(c) Use this to estimate the asymptotic running time of such a call

Remark: This is not important for your solution, but in this algorithm a/b represents
integer division, e.g., 5/3 gives 1.

int cantor(int low, int high) {


int gap=(high-low)/3;
if (high < low)
return 0;
else if (high == low)
return low;

3
cantor(0,15)
10 + 50 = 60

cantor(0,5) cantor(10,15)
1 + 9 = 10 21 + 29 = 50

cantor(0,1) cantor(4,5) cantor(10,11) cantor(14,15)


0+1=1 4+5=9 10 + 11 = 21 14 + 15 = 29

c.(0,0) c.(1,1) c.(4,4) c.(5,5) c.(10,10)c.(11,11)c.(14,14)c.(15,15)


0 1 4 5 10 11 14 15

Figure 1: Recursion tree for cantor(0,15).

else
return cantor(low,low+gap) + cantor(high-gap,high);
}

ANSWER: The correct interpretation of the “parameter” n is the gap (high−


low + 1) between the “high” and “low” values. Therefore:
(a) The recursion tree is found as Figure 1.
(b) The local work is constant, i.e., O(1) (computing the gap, testing the
if-statement, and performing the final addition). For the recursive part:
The gap in each recursive call is one third as big as in the input argu-
ments, i.e., we use parameter n/3 in both calls. Since there are two such
recursive calls in the code, adding these to the local work we get
T (n) = 2T (n/3) + O(1)

(c) Using the Master theorem (noting that 1 = n0 thus c = 0) we get


T (n) = Θ(n(log 2)/(log 3) ) = Θ(n0.63... )
Note: There are many equivalent ways of writing this, exponent, either
(logb 2)/(logb 3) for any base b (2, e, 10. . . ), or as 1/ log 3 using our
“default” base of 2, or as log3 2 as the book prefers.

3 (creativity question). At the very end of the slides for Lecture 2 it is announced
that there exists a “clever” algorithm for solving Maximum Subarray in time O(n)
(instead of O(n log n) as the divide-and-conquer scheme from the lecture results in). Can
you find this more clever algorithm? (It does not use any advanced paradigm. Just a
good observation.)
ANSWER: As the hint says on the lecture slide, try to maintain (in an it-
erative loop) a variable that stores the maximum value of a subarray that
ends at the current position (say i). There is a simple rule for this. The best
subarray in A that ends at position i is . . .

4
• The value of the best array ending at i − 1 plus A[i], OR
• just A[i], whichever is bigger.

That is, if we maintain a variable current best then the update step is
current best = A[i] + max(current best, 0).
This is shown as Kadane’s algorithm on Wikipedia (https://en.wikipedia.
org/wiki/Maximum_subarray_problem), where you can also find a detailed
description of it.

Question 4 is deferred until next week.

You might also like