CS307 Spring 2008 Midterm 2 Solution and Grading Criteria.
Grading acronyms:
ABA - Answer by Accident
AIOBE - Array Index out of Bounds Exception may occur
BOD - Benefit of the Doubt. Not certain code works, but, can't prove otherwise
ECF - Error carried forward.
Gacky or Gack - Code very hard to understand even though it works or solution
is not elegant. (Generally no points off for this.)
GCE - Gross Conceptual Error. Did not answer the question asked or showed
fundamental misunderstanding
NAP - No answer provided. No answer given on test
NN - Not necessary. Code is unneeded.
NPE - Null Pointer Exception may occur
OBOE - Off by one error. Calculation is off by one.
1. 2 points per question. No partial credit. On Big O question if answer is O(N)
N is acceptable. No constants or extra terms on Big O. For example if answer is
O(N) then O(4N) is counter wrong as is O(N + 10).
A. 9
B. 14
C. 6
D. O(N^2)
E. O(N)
F. O(logN) // base 3 okay
G. O(N^2)
H. O(N^3)
I. O(N)
J. O(N^2)
K. DECB // decb okay, quotes okay if included
L. 10 10 9 7 4
M. -2 0 2
N. 64 seconds
O. 21 seconds (2 * 10 * 21 / 20) okay. (20 is NOT acceptable.)
P overworked +1 point extra credit. Ignore differences in capitalization or if
two words instead of 1.
Commentary on short answer:
A. simple recursion.
B. must build the table to avoid repeated effort
C. recursively determines max value in array
D. No difference between best, worst, average big O case. All O(N^2)
E. Loops not nested so just O(N)
F. A different log N problem.
G. This is the bubble sort algorithm, No difference between best, worst, average
big O case. All O(N^2). (Bubble sort can be modified so it is O(N) best case if
data already sorted, but this version does not do that.)
H. Simple O(N^3)
I. Math.max O(1) so O(N) overall. N steps
J. Removing first element of an array based list is O(N) with all the shifting.
So N times through while loop. Average size of list is N/2. Average of N/2
shifts per remove. N removes. So (N^2)
K. Toy list code. Testing your understanding of what a list does.
L. "" The fact that it is linked list doesn't change the problem.
M. Toy stack code. Testing your understand of what a stack does.
N. Set up the proportion. 5000^3 / 20,000 ^ 3 = 1 / x
5000^3 / 20,000^3 = 5,000^3 / (5,000^3 * 4^3) = 1 / x
Algebra
x = 4^3 * 1 = 64
O. Needed to know Quicksort is N log base 2 N in average case and that log 2 of
1,000,000 is about 20.
P. Extra credit carrot from the day before spring break.
2. Commentary. This was intended to be a simple question involving linked lists.
Notice it was not necessary to alter the linked list in any way, merely traverse
it and check a few things. (In other words the method is an accessor, not a
mutator.) The question was much easier than either of the linked list quiz
questions which did require manipulation. You can bank on there being another
linked list question on the final.
For some reason linked lists are a bit of litmus test when it comes to
programming. The faculty that teach upper division courses (programming and
theory (Cs337), programming languages (CS345), and operating systems (CS372?))
expect you to understand and be able to program with linked lists. I have also
heard from several students that linked questions are quite common in technical
job interviews. The thinking may go, if they understand linked lists they
understand programming. Maybe because you have to understand things at the
language level and further down at the machine level. (Not quite the transistor,
but the memory.)
Common problems:
- off by one errors when using size to determine number of checks to make
- not actually traversing the list. Not moving the temp node
- not being clear on the difference between a temporary node, temp.getNext(),
and tenp.getData()
- comparing node variables instead of data. (The node is not the data. The node
holds the data.)
- the problem statement was not clear on whether the data had to be sorted in
ascending order or monotonically increasing. (equal values not allowed) so I let
that slide. (Although it was clear from the examples the data did NOT have to be
monotonically increasing.)
- I also didn't worry about head / tail instead of first / last
One other minor comment. This kind of thing makes me cringe. I don't know if it
is a result of time pressure or if it really shows a lack of understanding. I
didn't take any points off, but get OUT of the habit of doing this.
Node temp = new Node(0, null);
temp = first; // then why did you create the above object just to orphan it
// on the next statement?
Why not just:
Node temp = first;
This doesn't slow the program down that much or create a memory leak (in Java)
but it could be interpreted as a lack of understanding.
Suggested Solution
public boolean isAscending(){
boolean ascending = true;
if( size > 1 ){
int previous = first.getValue();
Node temp = first.getNext();
while(temp != null && ascending){
ascending = temp.getValue() >= previous;
previous = temp.getValue();
temp = temp.getNext();
}
}
return ascending;
}
Criteria:
- handle cases of size 0, 1 (1 does not have to be a special case): 3 points
- move through entire list: attempt 4 points, correct 6 points
- compare values: attempt 4, correct 6 points
- return result: 2 points
And then I saw this nifty solution which got rid of the special cases.
public boolean isAscending(){
Node temp = first;
int index = 1;
boolean sorted = true;
while( index < size && sorted ){
sorted = temp.getData() <= temp.getNext().getData();
index++;
temp = temp.getNext();
}
return sorted;
}
3. Commentary on the problem: A straight forward operation on an array based
list. The only subtleties were figuring out where the start of the second half
were and keeping track of two indices. (Current location in first and second
half of list.)
Common problems:
- some answers reversed the list or swapped the halves and reversed them.
- you could not use a temporary array. The solution had to be O(1) space
- you could not use other methods or classes
- a nested loop was NOT necessary and usually ended up mangling the array
- the elements of the list were Objects not int (confusing based on the example
I
know, so it was only 1 point)
Suggested solution:
public void swapHalves(){
int sizeOfHalf = size / 2;
int indexInSecondHalf = size / 2 + size % 2;
Object temp;
for(int index = 0; index < sizeOfHalf; index++){
temp = myContainer[index];
myContainer[index] = myContainer[indexInSecondHalf];
myContainer[indexInSecondHalf] = temp;
indexInSecondHalf++;
}
}
Criteria:
- determine how many elements need to be swapped: 5 points
- determine index of start of second half: 5 points
- loop through elements of list: attempt 3 points, correct 5 points
- swap element attempt 2 points, correct 5 points
There were some nice solution that just did the math in the array index.
4. A difficult recursion problem. Lots to keep track of. Going out of bounds,
what the target plant type is, and especially making sure we don't revisit
squares and end up in an infinite recursive loop. A lot of attempts looked good
at first, but had real problems.
Common problems
- only tracking the most recently visited cell. Doing this could still result in
an infinite recursive loop. (picture 4 cells in a box.)
- lots of problems in keeping track of size. Recall java parameters are value
parameters. If you send a parameter and change it in the method you call that
change is NOT reflected back to the original method
- not even attempting to keep track of cells we have visited.
- early returns resulting in only checking 1 direction
- not creating a helper method tin order to send target plant and information
about what cells have been visited
- altering the original map. (post condition stated the original map could not
be altered.)
- assuming plant values would not be negative. That was not a precondition.
Suggested Solution
public static int sizeOfPlantArea(int[][] map, int row, int col){
// to track where we have been
boolean[][] visited = new boolean[map.length][map[0].length];
// kickoff!
return areaHelper(map, visited, row, col, map[row][col]);
}
private static int areaHelper(int[][] map, boolean[][] visited, int row,
int col, int tgt) {
// 3 base cases
// out of bounds? return 0
if(row < 0 || row >= map.length
|| col < 0 || col >= map[0].length)
return 0;
// if I have been here before don't count it!
else if( visited[row][col] )
return 0;
// not the right plant? return 0
else if(map[row][col] != tgt)
return 0;
// recursive case. Mark that I have been here
// and check neighbors
else{
visited[row][col] = true;
return 1
+ areaHelper(map, visited, row - 1, col, tgt)
+ areaHelper(map, visited, row + 1, col, tgt)
+ areaHelper(map, visited, row, col - 1, tgt)
+ areaHelper(map, visited, row, col + 1, tgt);
}
}
Criteria:
- Create helper with parameters to track where we have been
and target number: 2 points
- make correct call to helper from initial method: 1 points
- out of bounds base case: 2 points
- have already been here base case: 4 points
- not the right plant base case: 2 points
recursive case:
- remember or mark that we have been here: 1 points
- track size of area: 3 points (calls must be correct
- check all four directions: attempt 2 points, correct 3 points