CS106B Midterm Review Anna Zeng and Aleksander Dash Slides adapted from Anton Apostolatos, Ashley Taylor and Nolan Handali Special thanks to Brahm Capoor for everything recursion-related
Today s Session Overview Logistics C++ basics ADTs Big-O Recursion Tips, tricks, and everything in between! Memory, pointers, and link nodes Additional study tips
Logistics
Midterm Logistics Thursday February 15th, 7:00-9:30 pm Memorial Auditorium Open book No electronic copies, no print-outs There will be a few copies in the front if you don t have a copy Closed notes (reference sheet will be provided) Pencils highly recommended, pens accepted Problems graded based on functionality, not style But if we can t read your handwriting, we can t grade you!
What s on the midterm Topics: C++ basics: Console I/O, strings, streams, functions and pass by value/reference ADTs: Vector, Grid, Stack, Queue, Set (+ HashSet), Map (+ HashMap) Big-Oh Notation Recursion Backtracking Pointers 8-10 questions total Mix between reading code and writing code Source: XKCD
What s not on the midterm
C++ Basics
Understand what Tip #0: happens when you pass by value vs. reference Make sample mystery Tip #1: code and test yourself and your friends Review assignments 1 and 2 for I/O, strings Tip #2: and file streams Source: International Obfuscated C Code Contest
Tracing Exercise int main() { int frodo = 7; int sam = 5; int merry = 4; int pippin = cervantes(sam, dumas(frodo, sam, merry)); cout << sam << endl; cout << pippin << endl; cout << merry << endl; return 0; int cervantes(int &sancho, int quixote) { sancho *= quixote; return quixote--; int dumas(int athos, int &aramis, int &porthos) { if (athos + aramis < porthos) { athos = aramis - porthos; else { porthos--; return aramis - (athos + 7);
Tracing Exercise int main() { int frodo = 7; int sam = 5; int merry = 4; int pippin = cervantes(sam, dumas(frodo, sam, merry)); cout << sam << endl; cout << pippin << endl; cout << merry << endl; return 0; Console -45-9 3 int cervantes(int &sancho, int quixote) { sancho *= quixote; return quixote--; int dumas(int athos, int &aramis, int &porthos) { if (athos + aramis < porthos) { athos = aramis - porthos; else { porthos--; return aramis - (athos + 7);
ADTs
43 Vector Grid (1D list of elements) (2D list of elements) 12 0-20 32 4 90 20 12 0 33
Stack Queue (LIFO linear structure) (FIFO linear structure) push pop 43 21-1 89 enqueue 3 0-1 78 dequeue
Map Set (Collection of key/value pairs) (Collection of unique elements)
ADT Exercise I Write a function that reads a input file of phone numbers and prints all the phone numbers with the most commonly occurring area code. Sample input.txt: 650-723-2273 206-685-2181 800-356-9377 800-347-3288 650-725-7411 520-297-6312 206-543-1695 800-266-2278 206-543-2969 Sample output: 206-543-1695 206-543-2969 206-685-2181
Solution (part 1) void areacodes(string filename) { // open file ifstream input; ifstream input(filename); input.open(filename.c_str()); if (input.fail()) { return; // read file data into map of sets Map <string, Set<string>> numbers; string line; while (getline(input, line)) { string areacode = line.substr(0, 3); numbers[areacode].add(line);
Solution (part 2) // find most popular area code string best = ""; for (string areacode : numbers) { if (best.empty() numbers[areacode].size() > numbers[best].size()) { best = areacode; // print all numbers in that area code for (string number : numbers[best]) { cout << number << endl;
ADT Exercise II Write a function commoncontacts(map<string, int>& mybook, Map<string, int>& theirbook) that returns a set of the names of all the common contacts (same name & number) between two phonebooks Chris Marty Mehran 685-9138 546-0166 950-3107
ADT Exercise II - Using vectors Set<string> commoncontacts(map<string, int>& mybook, Map<string, int>& theirbook) { Set<string> commoncontacts; for (string mycontact : mybook.keys()) { for (string theircontact : theirbook.keys()) { if (mycontact == theircontact && mybook[mycontact] == theirbook[theircontact]) { commoncontacts.add(mycontact); return commoncontacts;
Big-Oh Notation
Big-Oh Exercise I If vec has N elements and database has M elements: int mystery(vector<int>& vec, Set<int>& database) { int total = 0; Set<int> s = database; for (int itemone : vec) { for (int itemtwo : database) { if (itemone == itemtwo) { total++; else { for (int itemthree : vec) { s.add(itemthree); return total;
Big-Oh Exercise I - Answer: O(N²Mlog(M)) If vec has N elements and database has M elements: int mystery(vector<int>& vec, Set<int>& database) { int total = 0; // O(1) Set<int> s = database; // O(M) for (int itemone : vec) { // O(N * M for (int itemtwo : database) { // O(M if (itemone == itemtwo) { // total++; // else { for (int itemthree : vec) { // s.add(itemthree); return total; // O(1) * N * log(m)) * N * log(m)) O(1) O(1) O(N * log(m)) // O(log(M))
Big-Oh Exercise II If s has N elements: int recurse(stack<int>& s) { if (!s.isempty()){ int x = s.pop(); return x + recurse(s); return 0;
Big-Oh Exercise II - Answer: O(N) If s has N elements: int recurse(stack<int>& s) { if (!s.isempty()){ int x = s.pop(); return x + recurse(s); return 0; 1. 2. // O(1) // O(1) What is the Big-Oh of each recursive call? How many recursive calls do we make? Each recursive step is O(1), and since we go through the entire stack one element at a time, we have N recursive steps, so we multiply them to get O(1 * N)
Recursion Brahm Capoor In order to understand recursion, you must first understand recursion
Section problems 1. Problem 3: Reverse 2. Problem 5: Subsequence
Reverse Let s find the base case When is the problem so simple you don t need to think? What s the simplest possible string? Now let s do the harder cooler part If we have a more complex string, how can we make the problem simpler? reverse( banter ) = retnab key insight: we can break a string into two parts: the first letter, and the rest of the string How does this suggest recursion?
The Pseudocode function reverse(s): if s is blank: return a blank string else: c = first character of s r = rest of string return reverse(r) + c The Code string reverse(string s) { if (s == ) return ; return reverse(s.substr(1)) + s[0];
Subsequence: is s2 a subsequence of s1? Let s find the base case(s) When is the problem so simple that you don t need to think? What s the simplest possible string? How many base cases do we need? Now let s do the harder cooler part If we have more complex strings, how do we make the problem simpler? examples: issubsequence( catch, cat ) = issubsequence( atch, at ) issubsequence( scatter, cat ) = issubsequence( catter, cat ) key insights: if the two strings have the same first character, we examine the rest of the strings if the two strings have different first characters, we examine the rest of the first string
The Pseudocode function is_subseq(s1, s2) : if s2 is blank: return true if s1 is blank: return false else: if same first character: r1 = rest of s1 r2 = rest of s2 return is_subseq(r1, r2) else: r1 = rest of s1 return is_subseq(r1, s2) The Code bool is_subseq(string s1, string s2) { if (s2 == ) return true; if (s1 == ) return false; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return is_subseq(r1, r2); else { string r1 = s1.substr(1); return is_subseq(r1, s2);
Other useful recursion problems Tracing problem: Basic recursion practice: Problem 1 Problem 2: Sum of squares Recursion string problems: Problem 3: Reverse Problem 4: Star string
Backtracking Choose. Explore. Unchoose. Repeat.
Recursion vs backtracking: building an intuition string reverse(string s) { if (s == ) return ; return reverse(s.substr(1)) + s[0]; bool is_subseq(string s1, string s2) { if (s2 == ) return true; if (s1 == ) return false; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return is_subseq(r1, r2); else { string r1 = s1.substr(1); return is_subseq(r1, s2);
Recursion vs backtracking: building an intuition string reverse(string s) { if (s == ) return ; return reverse(s.substr(1)) + s[0]; bool is_subseq(string s1, string s2) { if (s2 == ) return true; if (s1 == ) return false; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return is_subseq(r1, r2); else { string r1 = s1.substr(1); return is_subseq(r1, s2); // the recursive cases are mutually exclusive In recursion, you only ever do one recursive call at every level of the recursion In recursion, you know that your recursive call will work (it s the leap of faith!)
Section problems 1. Problem 4: Longest Common Subsequence
issubsequence bool is_subseq(string s1, string s2) { if (s2 == ) return true; if (s1 == ) return false; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return is_subseq(r1, r2); else { string r1 = s1.substr(1); return is_subseq(r1, s2); subseq( banter, bar ) = subseq( anter, ar ) subseq( banter, ant ) = subseq( anter, ant ) Let s find a base case When is the problem so simple you don t need to think? What s the simplest possible string? Let s think about more complicated versions of the problem Let s try and mirror the structure of bool is_subseq(string s1, string s2) What s the LCS when the two strings have the same first character? What s the LCS when the two strings have different first characters?
LCS redux: so why backtracking? bool is_subseq(string s1, string s2) { if (s2 == ) return true; if (s1 == ) return false; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return is_subseq(r1, r2); else { string r1 = s1.substr(1); return is_subseq(r1, s2); subseq( banter, bar ) = subseq( anter, ar ) subseq( banter, ant ) = subseq( anter, ant ) Let s find a base case When is the problem so simple you don t need to think? What s the simplest possible string? when strings are blank! Let s think about more complicated versions of the problem Let s try and mirror the structure of bool is_subseq(string s1, string s2) What s the LCS when the two strings have the same first character? LCS(the rest) What s the LCS when the two strings have different first characters?
LCS redux: so why backtracking? What do we do when the two strings have different first characters? LCS( banter, ant ) = LCS( anter, ant ) How did I know to remove the b from banter? LCS( banter, abat ) = LCS( banter, bat ) How did I know to remove the a from abat? Radical idea: What if I didn t know? What if I tried both ways? I d get two subsequences. How would I know which was better? Hint: What is the function trying to find? Let s put it all together...
The Pseudocode Compare: issubsequence Pseudocode function LCS(s1, s2) : if s1 or s2 is blank: return a blank string else: if same first character: c = first char r1 = rest of s1 r2 = rest of s2 return c + LCS(r1, r2) else: r1 = rest of s1 r2 = rest of s2 p1 = LCS(s1, r2) p2 = LCS(r1, s2) return longer of p1 and p2 function is_subseq(s1, s2) : if s2 is blank: return true if s1 is blank: return false else: if same first character: r1 = rest of s1 r2 = rest of s2 return is_subseq(r1, r2) else: r1 = rest of s1 return is_subseq(r1, s2)
The Pseudocode function LCS(s1, s2) { if s1 or s2 is blank: return a blank string else: if same first character: c = first char r1 = rest of s1 r2 = rest of s2 return c + LCS(r1, r2) else: r1 = rest of s1 r2 = rest of s2 p1 = LCS(s1, r2) p2 = LCS(r1, s2) return longer of p1 and p2 The Code string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ string r1 = s1.substr(1); return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length() { return p1; else { return p2;
Rethinking backtracking It s hard to think of this in terms of choose/explore/unchoose But what did we do? string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
Rethinking backtracking It s hard to think of this in terms of choose/explore/unchoose But what did we do? We found the possible options string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
Rethinking backtracking It s hard to think of this in terms of choose/explore/unchoose But what did we do? We found the possible options We figured out what the best one was string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
Rethinking backtracking It s hard to think of this in terms of choose/explore/unchoose But what did we do? We found the possible options We figured out what the best one was We gave that one back string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
Rethinking backtracking It s hard to think of this in terms of choose/explore/unchoose But what did we do? We found the possible options We figured out what the best one was We gave that one back Backtracking isn t about choosing, exploring and unchoosing It s about figuring out which option works best (sometimes, that means choosing, exploring and unchoosing) string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
That said... Choose (kinda) string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
That said... Choose (kinda) Explore (kinda) string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
That said... Choose (kinda) Explore (kinda) Unchoose (kinda-er) string LCS(string s1, string s2) { if (s1 == s2 == ) return ; if (s1[0] == s2[0]){ return s1[0] + LCS(r1, r2); else { string r1 = s1.substr(1); string p1 = LCS(s1, r2); string p2 = LCS(r1, s2); if (p1.length() > p2.length()) { return p1; else { return p2;
Other useful backtracking problems (all of them) Working with the choose-explore-unchoose paradigm: Using Recursive helper functions: Problem 6: Make Change Problem 9: Ways to climb Batsh!t awesome problems: Problem 7: Print Squares Problem 10: Twiddle Problem 11: Hacking & cracking (So literally, all of them. These are awesome midterm practice)
endl; /* good luck with the midterm! */
Pointers and Linked Nodes
Linked Lists 101 int main() { ListNode* front = new ListNode(); front->data = 3; delete front; Use new to create a new object on the heap To access data on the other end of a pointer, use -> Remember that this is just the same as, (*front).data Need to delete whatever you create Otherwise, memory leaks!
struct ListNode { int data; ListNode* next; Linked Nodes practice list1 list2 1 4 2 3 5 (8 out of 9 practice midterms have some form of this as the linked list problem) Tips: Draw a picture! Potentially declare temporary pointers to help you rearrange nodes
Linked Nodes practice answer ListNode* temp = list1; list1 temp list2 ListNode* temp2 = list2; 2 1 4 3 5 temp2 list1 list1 = list1->next; 1 2 3 temp 4 list2 = list1->next; temp2 5 list2
Linked Nodes practice answer list1 list1 -> next = temp2 -> next; list1->next->next = temp; list1->next->next->next = nullptr; temp temp2 1 4 list2 ->next = temp2; list2->next->next = nullptr; list2 3 2 5
Pointer trace tips: The * symbol has two uses type* ptrname declares a pointer that points to an object of type type e.g. ListNode* front is a pointer that points to a ListNode *ptrname dereferences ptrname (aka, goes to where the pointer is pointing to) We ve seen this! Recall that ptr->data is equivalent to (*ptr).data
Pointer trace tips: The & symbol has two uses &value means give me the memory address that value is stored at e.g. &value returns something that looks like 0x20065ab0 Don t confuse this with reference parameters in function prototypes! e.g. void printlist (Vector<int>& v) {...
Pointer trace practice (from Practice Midterm #9) What is the output?
Pointer trace practice (from Practice Midterm #9) What is the output? Console 203 3005 11 3003 16 204 11 204 3003 16
Additional Study Tips Good practice materials: Practice exams (9 total) Leftover section problems Codestepbystep.com See Julie Zelenski s Exam Strategies handout (bottom of cs106b.stanford.edu/exams.shtml) You got this! Good luck!