## Announcements

• Last lecture before the exam!!
• In two weeks, cause no class next week
• We will have a short (20 minute) review immediately before the exam
• I will try to give you some study materials/ideas of what the exam will be like in the next couple weeks
• Feedback on lecture quiz answers
• Be sure to answer all parts of a question
• Be sure your answer is at the right level of abstraction
• If I ask about something general, don’t explain something concrete (except as an example)
• “Call” and “return” are very specific terms in programming
• Call is when program control transfers to another method/function
• Return is when program control transfers back to the method that called this one
• You cannot call variables, classes etc.

## Divide-and-Conquer

• We can solve some problems by splitting them into subproblems that are a fraction of the original size
• Each subproblem can then be split further into smaller fractions
• Then we can combine the results of the subproblems to get a final result
• Going back to integer exponentiation
• Iterative definition was “multiply product by B, N times”
• Recursive definition was:

• Remember that recursion is just another way to write a loop
• So does this solve the problem any faster?
• No, we still recurse N times
• Each subproblem is just a constant size smaller - $(N - 1)$
• How would we make each subproblem a fraction of N?
• Defining it in a divide-and-conquer way
• Remember this rule of exponentiation: $(B^x)^y = B^{x \times y}$
• How do we divide each step?
• If we have $B^8$, that’s $B \times B \times B \times B \times B \times B \times B \times B$
• But the two halves of the computation look the same
• Why bother doing $B \times B \times B \times B$ twice?
• Why not do that once and then multiply it by itself?
• Multiplying something by itself is also known as squaring
• This becomes our combine step.
• So the idea is…
• To do $B^N$, do $B^{N / 2}$, and then square that.
• What if N is odd?
• Then we have to take the “extra 1” out of the exponent, leaving us with an even number
• And finally, what is the base case?
• Just like last time, $B^0 = 1$
• Putting it all together…

• What is the runtime of this?
• Each step involves cutting the problem in half.
• If we start at $N=32$, then we’d to 16, 8, 4, 2, 1, 0
• We’re kind of answering: what power do we raise 2 to, to get N?
• That’s a logarithm!
• So it’s $O(log\ n)$
• Could you write this iteratively?
• Yes, absolutely.
• But honestly, it’d be kind of awkward.
• Another divide-and-conquer example: binary search
• You know how to search an array linearly (look at every item in order)
• But if we have an array that is in sorted order, we can find things much faster.
• Think about a dictionary. Do you start searching at the beginning?
• No. You kind of “weed out” portions of the book where the word can’t be.
• This is the idea behind binary search:
• Split the array into two halves with one value between them
• Look at that one value
• If it’s the value you’re looking for, you’re done.
• If it’s bigger than the value you’re looking for, look in the smaller side.
• If it’s smaller than the value you’re looking for, look in the bigger side.
• If it’s not in either side, it’s not in the array.
• And if the array is empty, it’s not in the array.
• Let’s say we’re looking for 17 in this array:

• Split the array into two (roughly) equal halves with one value between:

• That middle value is not what we’re looking for, but it’s bigger. So can 17 be in the right half?
• NO!
• Repeat the process with the left (smaller) half. (Let’s split the difference by making the left side bigger.)

• Now 9 is too small. Repeat with the right half.

• There it is!
• If I were looking for 18, I’d look in the right side… but the right side is empty, so it would not be found.
• Again, we’re throwing out half the problem on each step.
• So again, this is $O(log\ n)$.
• And again, you can do this iteratively.
• It’s a little tricky, but possible.

## Multiple recursion

• So far, the recursive problems we’ve looked at only require one recursive step each time
• Both exponentiation methods do one recursive exponentiation in each case
• Binary search only looks in one half of the array in each case
• But some problems require doing multiple recursive calls in at least one case
• Suppose we wrote a recursive search for an unsorted array
• We could do the same “split in half, look at the middle item” approach…
• But since the array is unsorted, we’re forced to look in both halves of the array
• Granted this is a silly way of doing it, but it illustrates the concept
• We’ll come back to multiple recursion later when we discuss trees
• And we’ll see where recursion really shines

## Tail recursion

• Singly-recursive functions can be trivially converted into loops
• Essentially they are using the call stack as temporary storage
• An implicit stack data structure! Built into the program!
• Some languages/compilers (not Java…) can recognize this
• And instead of nesting another function call…
• They replace the current function call with the new one
• This prevents call stack overflows
• You can recurse an infinite number of times and the call stack will stay the same size.
• This is restricted to recursive calls of the form: return func(...)
• If you do anything after the recursive call, it’s not possible: return func(...) + 1
• Why is this important?
• Well… our computers have co-evolved with imperative, iterative programming languages
• They’re really good at running loops quickly!
• They’re not as good at running recursive functions.
• Every function call pushes a good deal of data onto the call stack.
• Even in a tail-recursive call, we’re essentially doing a pop followed by a push.
• This takes a lot more time and space than just running a loop.
• So is recursion useless, if we can just rewrite everything as loops?
• Well, no.
• Again: it’s a problem-solving tool.
• Having multiple tools in your repertoire is useful.
• Some problems are just easier to express recursively.
• And some multiply-recursive functions are very difficult to write as loops.
• You have to get a separate Stack or Queue (ooo) involved.

## Backtracking

• The last recursion topic!
• And also what you’ll need to know for project 3…
• The book and everyone and their brother uses this “Eight Queens” problem as an example but
• idk
• I never played chess
• It seems like a pointless problem to solve
• Instead let’s consider this: given a money value, list all the different ways you can combine coins and bills to represent that value.
• It’s gonna be a lot, isn’t it?
• One possible “solution” to this problem is gonna be a list of values, where each item in the array is “how many of that coin/bill do you need?”
• If we only had coins (pennies, nickels, dimes, quarters) and wanted to list the ways we could represent 10 cents:
• $\{10, 0, 0, 0\}$ (10 pennies)
• $\{ 5, 1, 0, 0\}$ (5 pennies, 1 nickel)
• $\{ 0, 2, 0, 0\}$ (2 nickels)
• $\{ 0, 0, 1, 0\}$ (1 dime)
• An invalid solution would be like $\{11, 0, 0, 0\}$
• That’s more than 10 cents.
• Let’s think about how we might solve this:
• Start at $\{0, 0, 0, 0\}$.
• Increase the number of pennies until we get to the “goal”.
• Stop increasing the number of pennies. Stop it.
• Then back up
• Take the pennies away until we can increase the number of nickels
• Once we find a solution involving at least one nickel…
• back up again.
• Eventually, we’ll find all the solutions.
• The backtracking template
• Every backtracking problem will follow this basic template:

  void solveProblem(currentAttempt) {
return;

if(currentAttempt is a valid solution)
output currentAttempt;

for(each possible nextAttempt based on currentAttempt)
solveProblem(nextAttempt)
}

• The parts work like this:
• is a dead end means “this attempt is invalid, and any solution based on it would be invalid.”
• e.g. if we’re looking for 10 cents and we get 12, there’s no way to add more coins and get 10 cents.
• is a valid solution means “this attempt solves the problem.”
• e.g. we found a combination of coins/bills that totals to 10 cents.
• a next attempt based on the current attempt means that we modify the current attempt in some way to get “closer” to the goal.
• e.g. we only have 4 cents, and want to get to 10, so we add one coin/bill.
• in the coin problem, once we find a solution, there are no “next attempts” so this loop will not run.
• Since we can make many recursive calls, this is multiply-recursive
• Trying to solve a problem like this iteratively would be… nightmarish.
• The “backing up” happens implicitly
• Whenever we return from a recursive call, we’re “backing up” to a previous attempt
• You will go into this technique more deeply in 1501