CS 330: Algorithms
Spring 2016
Lab 8- Solution
Instructor: John Byers
Question 1. You’re taking n courses and every single one of them has a final project that you still have
to get around to finishing up. Each project will be graded on the following scale: it will be assigned an
integer number on a scale of 1 to g > 1; with higher numbers being better grades. Your goal, of course, is
to maximize your average grade on the n projects. Because it’s the end of the semester and you’ve gotten
into some bad habits, you realize that you procrastinated too much and now you only have a total of H > n
hours in which to work on the n projects cummulatively and you need to figure out the best way to divide
up this time to maximize your average grade overall. For simplicity, assume that H is a positive integer and
you’ll spend an integer number of hours on each projects.
To figure out how best to divide up the time, you’ve come up with a set of functions [fi : i = 1, 2, · · · , n]
for each of your courses: if you spend h ≤ H hours on the project for course i, then you’ll get a grade of fi (h).
You may make the assumption that the functions are non-decreasing (i.e if h < h0 , then fi (h) ≤ fi (h0 )).
Given these functions, decide how many hours to spend on each project (in integer values only) so that your
average grade, as computed according to the fi , is as large as possible. The algorithm should be polynomial
in n and H.
Solution:
We can first note that it is equivalent to maximize one’s total grade over the n courses this is because the
average is just dividing the total grade by n. Suppose we have a matrix A which is n×H. In other words, each
entry A[i, h] represents the maximum grade for taking i courses and spending h hours on the final projects
i
P
total. We can first observe that A[0, h] = 0 for h = 0, · · · , H and A[i, 0] =
fj (0). We can fill in the rest
j=1
of the matrix entries by finding k such that A[i, h] = max (A[i − 1, h − k] + fi (k)). In other words, we are
0≤k≤h
trying to find the best number of hours k to devote to class i so that the sum of the grade in that class plus
the grade in classes (1, · · · , i − 1) using only h − k hours is maximized. We will keep track of each of the k
that we select.
It is important to note that with such a way of computing the grades, the final grade that we will receive is
the entry A[n, H]. Using the recorded k values, we can back-trace and see how many hours we spent for each
course. The total time to fill each entry is O(H) and there are nH entries. Thus, we have a total running
time of O(nH 2 ).
Question 2. We will take a look at an example of the number partitioning problem given in problem 4
of homework 5. Essentially, we have a set A of non-negative integers {a1 , a2 , · · · , an } and want to assign a
n
P
sign (positive or negative) S = {s1 , s2 , · · · , sn } to each of the numbers such that the residue u = si ai i=1
is minimized.
(a) Suppose we have the set A = {1, 2, 1, 2, 1, 3}. What is a good assignment of signs such that u is
minimized?
(b) Come up with a set of size 4 that has u = 1.
(c) Try to find both a recursive and dynamic programming version of the verification version of this problem.
Essentially, come up with a way to simply check to see if it is possible to split the given set into two
-1
subsets such that the sum of the two subsets is equal. This algorithm will only return true or false.
We can observe two things very quickly about this problem: (1) if the sum of the whole array is
odd, then there cannot be two subsets whose sum will be equal and (2) if the sum of the array elements
is even, then we can calculate sum/2 and find whether or not there is a subset of the array with sum
equal to sum/2. It will be helpful for you to start with a recurrence relation and then develop how the
algorithm will work. For the recursive case, recall that the problem is NP-hard so you should try a
brute-force method and it will yield an algorithm with an exponential running time. For the dynamic
programming case, we can think about creating a matrix where the columns correspond to the indices
of the array and the rows correspond to the sums starting from 0 until sum/2.
Solution:
(a) Let A1 = {1, 1, 1, 2} and A2 = {2, 3}. We will assign all the numbers in A1 a positive sign and all the
numbers in A2 a negative sign. Thus, we can see that u = |1 + 1 + 1 + 2 − 2 − 3| = 0.
(b) Let A = {2, 2, 1, 2}. We can only split this as A1 = {1, 2} and A2 = {2, 2}. Suppose we assign a negative
sign to A1 . Thus, we get u = |2 + 2 − 1 − 2| = 1.
(c) The recursive solution would consider either including the last element as part of the sum or excluding
the last element as part of the sum. The pseudo-code will be as follows:
def f i n d I f P a r t i t i o n ( a r r ) :
a r r s u m = sum( a r r )
i f a r r s u m % 2 != 0 :
return f a l s e
return i s S u b s e t S u m ( arr , len ( a r r ) , a r r s u m / 2 )
def i s S u b s e t S u m ( arr , i , t o t a l ) :
#t o t a l = 0 can o n l y happen once we h a v e f o u n d e l e m e n t s
#t h a t sum up t o what we r e q u i r e
i f t o t a l == 0 :
return t r u e
# i f we a r e o u t o f a r r a y e l e m e n t s b u t h a v e
#n o t y e t c r e a t e d t h e t o t a l we n e e d e d
i f ( i == 0 and t o t a l != 0 ) :
return f a l s e
# i f t h e l a s t e l e m e n t i s g r e a t e r t h a n sum , t h e n i g n o r e i t
i f ( a r r [ i −1] > t o t a l ) :
return i s S u b s e t S u m ( arr , i −1, t o t a l )
#e i t h e r i n c l u d e l a s t e l e m e n t or n o t
return i s S u b s e t S u m ( arr , i −1, t o t a l ) | |
i s S u b s e t S u m ( arr , i −1, t o t a l − a r r [ i −1])
The running time here is O(2n ) because we are trying two possibilities for every n elements.
In the DP version, we will keep a boolean 2D array of size half of the sum of the array elements by (length
of the array + 1) called isSub where an entry isSub[i,j] will be true if the sum of {arr[0], · · · , arr[j − 1]}
is equal to i and otherwise false.
def f i n d I f P a r t i t i o n ( a r r ) :
a r r s u m = sum( a r r )
-2
i f a r r s u m % 2 != 0 :
return f a l s e
# i f t h e sum i s 0
#we h a v e a l w a y s a c h i e v e d t h i s sum ( t r i v i a l c a s e )
for i = 0 . . . len ( a r r ) :
isSub [ 0 ] [ i ] = true
# i f we a r e n o t c o n s i d e r i n g any p a r t
#o f t h e a r r a y , t h e n we w i l l n e v e r r e a c h t h e sum
for i = 1 . . . a r r s u m / 2 :
isSub [ i ] [ 0 ] = f a l s e
for i = 1 . . . a r r s u m / 2 :
for j = 1 . . . len ( a r r ) :
i s S u b [ i ] [ j ] = i s S u b [ i ] [ j −1]
i f i >= a r r [ j − 1 ] :
isSub [ i ] [ j ] = isSub [ i ] [ j ] | |
i s S u b [ i − a r r [ j − 1 ] ] [ j −1]
return i s S u b [ a r r s u m / 2 ] [ len ( a r r ) ]
The running time here is O(sum ∗ n), where sum is the total sum of the array and n is the length of the
array.
-3
© Copyright 2026 Paperzz