Four example problems are worked. Topics include solving recursively, O(n)-time dynamic programming, topological order, and subproblems.
Instructor: Justin Solomon
JUSTIN SOLOMON: So today, we're going to continue in our discussion of dynamic programming. I actually found this set of problem session problems to be easier than the previous one. There's a funny thing, which is we learned in class about pseudo polynomial time style dynamic programs. Somehow, that language is a little bit liberating, in the sense that you're using parameters that you really shouldn't, when it comes to the runtime of your algorithm. Well, I suppose we should, in the sense that it's allowed, if you call your algorithm pseudo polynomial time.
But it somehow makes it a little easier to formulate your dynamic programming algorithm, because all the numbers are staring you right in the face. You don't have to be so careful about what's fair game and what's not, when you post your algorithm so long as it's efficient in the values that you care about. And so today's problem session has five problems squeezed instead of the usual four.
We'll see how far we get, seeing that I usually talk too much anyway. But I'll try to stay on schedule here, and we'll see how well we do. Any questions from our students about dynamic programming before we get started here?
AUDIENCE: You can cut one, if you want.
JUSTIN SOLOMON: We can cut one? Oh, I will gladly cut one if I run out of time. Yeah. OK, so without further ado, let's get started with coin-crafting, which is problem 9-1 here. So I suppose it should be Ceal Naffrey if I were to work through my splinterism properly here. In any event, we have a thief who's in desperate need of money, as with many thieves, or else, of course, they wouldn't resort to the world of prime. And Ceal Naffrey here has n identical coins.
I've been very self-conscious about the way that I write the letter I, ever since Eric pointed it out, and it's only gotten worse. Now it's inconsistent and hard to read. But in any event, so we have n identical coins. And of course, these coins have very distinctive markings. And so we can't possibly run away with them as is, because if you take them to your standard jeweler, they'll immediately recognize that these markings are bad and stolen. And that's not so good.
So instead, we can melt these coins into other objects. So our sneaky thief here has identified a potential buyer. And the buyer has a few criteria here. So we have a buyer, and the buyer has a very strange value system thing, that apparently, it's easy to take coins and make them into other things.
But in any event, the buyer, what they care about is not the fact that the coins are made of gold, but rather that they like particular objects better than others made out of said gold. So in particular, they have a different rate for each object, a different price they're willing to pay. And so they have a list of n objects that they're interested in. And each one is associated with two things of n objects.
They have a price-- so the amount that the buyer is willing to pay for that object, which, again, is not just the weight in gold, for some reason. There's a value added tax in this universe. And it takes a different number of coins to manufacture. Right?
So maybe I can make a golden chocolate fountain, and that takes 10 coins. But I don't want two of those. So if I make that, then the next thing I have to do is a figurine of Eric and Jason to put alongside it. And having more than one of those would also be creepy and weird. So I can only make one of each object.
And of course, my goal here is I have n coins. By the way, the fact that there's n coins and n objects for the buyer doesn't really matter. I mean, it will for the runtime. But you could imagine this being n and m. So I wouldn't be too hung up on that.
And what you're trying to do is, of course, maximize your revenue, subject to the constraints that you have n coins, and you can't make two objects that are the same. Hopefully, I've captured the essence of our problem OK. OK, fabulous.
So as with all of our dynamic programming problems in 6006, we have a paradigm for how to approach them, which has a cute acronym, which is SRTBOT And I think SRTBOT is a totally relevant and straightforward approach to this problem here. Yeah, so in particular, let's give ourselves a bit of notation. So we're going to number our objects between 1 and n, just for convenience.
So we'll say that Pi is the price of object i. OK. And we'll say that Ki is what the problem calls the melting number. In other words, if I want to make object i, this is the amount of coins that I'd have to melt to make that object. OK? So just a tiny bit of notation.
And in general, so OK, what do we do when we formulate our dynamic programming problems? Some problems we could solve that are smaller. And when we compose them all together, we get the final solution to our problem. And this particular problem, involving manufacturing objects out of coins, I think is a really classic one, when it comes to dynamic programming. This is the sort of thing where someday, when you're trying to pay for your tuition by doing these hacker contests online, this is the sort of thing that comes up all the time in that universe, right?
So in particular, the two variables here, when I make a new object, is what object did I make? And how many coins did I spend when I did that? So those are the two natural parameters to use, when I solve my dynamic programming problem. Yeah.
And of course, it's going to be recursive, in the sense that I can either choose to make object i or not. And it doesn't matter what order I make my objects in or similarly, what order I spend my coins in, which is usually a nice property to have in a dynamic programming universe. So in particular-- oh, this thing is my nemesis. This is front. I think this class is particularly tough for short people, because it's moving up and down all the time.
OK, so given our observation here, if we're doing SRTBOT, so what's our S again? Let me ask myself that-- Sub-problems. Thank you, Professor Demaine. Then essentially, what we want to do, based on what I argued verbally, is maybe define our variable x i, j the thing we're going to compute, to be the revenue from using i coins and objects 1 to j. OK.
So in other words, I have i coins left in the bank, and I'm only allowed to use the first j objects. And we can already see that this is kind of a sensible way to approach this dynamic programming problem, in the sense that there's an obvious recursion here. I choose to make an object. I have fewer coins. And I can sort of imagine there being a topological order, in the sense that I could first decide about object 1, and then object 2, and object 3, and so on, or vice versa, depending on whether you're a prefix or suffix kind of guy-- which I still get backward.
But the good news is that it doesn't really matter. What matters is formulating the equation. OK, any questions about our definition of the thing that we're going to chase after here? Fabulous. Ah-- OK, right.
So let's continue SRTBOT. So our next piece of our puzzle here is the R. I believe R stands for recursion. This is a new acronym for me, too. Ope, no, Relate. But it might as well be Recursion for most of these problems. Right, so the basic relationship here is that, of course, I can either use object j, or I can not use object j.
And in both of those cases, If j is the very last guy that I'm going to consider, that'll be a totally reasonable recursive rule, right? So in particular, I have that xi, j essentially can take one of two values. That's a parenthesis, in case you're wondering. And of course, you're trying to maximize your revenue. So let's do that, OK?
So we have two potential options. But we have to be a bit careful. Is there a case where I can't make object j? Yes, class, there is, which is the case where I don't have enough coins left in the bank, right? So I want to make that really expensive fountain, but I only have one coin. Then I'm out of luck, yeah?
So let's do these two cases. So first, if-- oops, I got my cases backward. That's OK. Right. So let's say that I choose not to make object j. OK, so what does that mean? So did I spend any coins? No.
And moreover, what is my maximum profit? Well, it's going to be the same as the maximum profit, using objects 1 through j minus 1, because I didn't use object j. Yeah? So in particular, that would be x. Let's see, I got it backward in my notes, so we're going to do it live, like that. OK?
And otherwise, let's say that I did choose to make object j. Well, what happened? So I did get some revenue now, right? Man, what on earth did I write on these notes? So I get the price of objects j here as my revenue. But I spent some coins in the process. So I have i minus K sub i, which is the number of coins. What's that?
AUDIENCE: K sub j.
JUSTIN SOLOMON: Oh, thank you-- sorry, K sub j. That's why we should be consistent with our indices when we write these things down. So I spent K sub j coins making this thing, object j. And moreover, I can still choose to make any of the previous objects. So it's still just j minus 1.
But I have to be careful, because I can't always do this. In particular, this had better be a positive number for n, or at least a non-negative number. Because if I end up with a negative number of coins, well, that that's not a physical universe that I choose to be in. So in particular, what we need, in some sense, is i minus k minus j, and k and j to be greater than or equal to 0. Or equivalently, i is greater than or equal to K sub j. I think you guys could all do that one at home.
OK, and this is our recursion. Hopefully, I've gotten it right, because it disagrees with the crazy thing I wrote in my notes at 1:00 AM yesterday. But I think it's pretty straightforward. Essentially, either I can choose to use the last object, or I choose not to. And either one of those, of course, decrements j, because that's the index of the object I'm considering. And I either account for the price, but have to pay in gold, or I don't account for the price. So implicitly, there's a 0, and I don't have to pay in gold. OK, good.
All right, so let's continue with SRTBOT. We might, later in the session, relax going through every one of these steps, because a lot of the arguments are similar. But for now, we'll do one or two carefully. So T, I believe, stands for Topological order. And here, it's staring us in the phase. Because notice that xi, j only depends on x question mark, comma, j minus 1. [LAUGHING] Right?
So of course, on your problem set, you should write things more carefully. But the basic point here is that there's a clear topological order, just by looking at that second index. Because if you think of all your x's as variables in a graph, which we've actually drawn in lecture-- so maybe these are all the i's and then the j-- so i goes down, and j goes to the right. Then essentially, this argument is saying that all the arrows point left in this graph.
I suppose the way I've drawn my arrows isn't quite accurate. But it actually doesn't matter. The only thing that matters is that it goes from right to left. But I'm going to erase this, so you don't remember it. OK.
So in general, just when you want to make your topological order argument, I think the totally sensible one is looking at the indices of recursion and then just trying to find some number that decreases. Incidentally, if you take a differential equation course, that's roughly how you prove that a lot of those things converge, too. So there's a generic math check that we use a lot. What do you call that in ODE where have some number that decreases?
AUDIENCE: I would call that potential function.
JUSTIN SOLOMON: Potential function-- that's a perfectly sensible one. It's not Lipschitz. It's some other mathematician. Anyway, OK, so let's continue SRTBOT. So next we need B, which is our Base case. So in this case, it's pretty straightforward. If I don't have any coins, I can't make any money. I have a t-shirt that says that at home.
And moreover, if I can't sell anything, I can't make any money. So those are pretty straightforward cases. So we have that 0 equals x of 0, comma j. Remember, the first index is the number of coins you have. So this is saying, I can't make anything. I don't have any coins.
And similarly, equals x i, comma 0 for all i, j. So this is the coins and the objects. OK. So let's see. I keep writing these problem sessions too big and then spending half of it erasing. So let's try and fit this on one board here.
So we're going to do SRTBOT. Then the second-- no, the first O, because there's no O in SRT-- is the original problem that you want to solve. So of course, you start out with n objects and n coins. So the original problem we want to solve is equivalent to computing x income n. And then finally, we've got to do our runtime. T stands for runtime, or Time, I suppose.
So first of all, how many problems are there? Well, there's x i, j. Both I can go from 0 to n. This is a great way to be off by 1. So there's n plus 1 squared sub problems.
And how much work does each sub-problem do? Well, it does boring work. It's just a formula. Right? So there's order 1 work per sub-problem. So the overall algorithm takes n squared time.
So I promised to do something in our last problem session. Then I didn't actually do it. So I did think I would spend just a minute here translating what this SRTBOT thing would mean in terms of code. Because I think that it's a little bit implicit here. In particular, I think this step here, I mean, you will see that it really clearly is going to give you an algorithm. But I think it's kind of easy to, again, just to forget what your code actually looks like.
And actually, the coding problems on these problem sessions are almost too interesting and can obscure it a little bit. So I thought we'd do a boring problem and show you that it's really not so hard to do this. And in fact, we covered two different strategies in class for how to take SRTBOT and convert it into a piece of code. Although, they might have zipped past you in this 2x speed thing that you can do now.
So here are two options. One of them is called memoization. And the other, I don't know, bottom up, I guess, is a reasonable phrase to describe. And so I thought we'd do them both, because they're both easy for this particular problem. OK. Right. So let's do that.
So is this necessary on your homework? Strictly speaking, no. If you've gone through SRTBOT, then essentially, everything that happens after that is boilerplate, in terms of converting these steps into a piece of code or an algorithm. But I do think, just for understanding why SRTBOT makes sense, it's worth thinking about for a minute.
So option A here is memoization. Ironically, I've taught and TA'd algorithms a few times, and I never actually knew what memoization meant. So I learned something from Eric's lecture the other day. Which remember, memoization is the key thing in what, apparently, is a made-up word, is memo-- if you're back in the day, and you had a steno pad, and you were writing down stuff. Because that's how you solved problems, with your slide rule.
Then essentially, the idea is that, if I compute x i, j for some i, j pair, I shouldn't compute it again. I should just write it down on my memo pad. Stenoization sounds better to me. But I suppose we'd have to go back to the 1940s and fix it. OK, so let's actually write down. I'm going to write down pseudocode, that will probably look more like Matlab, because I'm that kind of guy.
So let's say that I wanted to make a function, which, I guess, is revenue of i, j, like that. Bah. And this is the thing I wanted to compute. That's actually going to be a problem, because we're not going to be able to see. OK, right.
So in addition to this, I'm going to pass in an array x, which is going to be like my memo pad. This is going to be terrible coding practice, but easy board coding practice. So if I were in C++, maybe I'm passing by reference, so that when I edit-- that's a treble clef, but whatever-- when I edit x, it actually persists when I recurse.
This is terrible coding practice, and you shouldn't do it. OK? But it's just going to be because I don't want to write too many lines on the board here. And maybe we initialize. We have some helper function to-- let's see, we want our revenue to be big. Well, actually no. We'll just initialize it to not a number, so that we know that we haven't computed it yet. How about that?
OK, so what should we do? Well, if we're going to memoize, the first thing we should do, any time that I call my revenue function on an i, j pair is check that I've already computed it. Yeah? So in my goofy, bad board-coding style here, what could I do? I'd say, well, if I've already computed it, then this thing won't equal NaN anymore and won't be not a number.
So I can say, OK, if x is not equal to not a number-- so in other words, it is a number-- return. And this, I think that this little line of code here, it gets a little lost. But this, we should put sparkles around it. This is the magic of dynamic programming, because I just killed recursive calls.
Even if i and j are 17 and 23, if I already computed it, I'm done, right? I don't have to call my recursion again. OK. And otherwise, what am I going to do? Well, otherwise, I'll maybe call-- do I want to write it all down? I don't want to write it all down.
So otherwise, I'm going to evaluate, R, where R is this formula over here. Notice that this will require recursive calls, right? And I'm going to store it in x i, j and then return x i, j. OK?
So basically, the only difference between what we've seen in the first 2/3 of 6006 and now is this beautiful line of code, saying, if I already computed this thing, return it. And this is the memoized version of our algorithm. I think this is the easiest one to maybe think about.
But actually, from a runtime analysis, it's a little bit annoying. It's not in the sense that we convinced ourselves that SRTBOT is OK. But of course, if you're thinking about your recursion tree, what's happening is that you're maybe convincing yourself that this piece can be lopped off in your function calls. So you have to do your counting carefully.
There's a different way to implement the same thing. So this would be option B. This is maybe more efficient, maybe less efficient, depending on your problem. But these are all within constant factors of each other, for the most part-- not always, but for the most part. This would be bottom up.
And this is the idea of, rather than just taking our recursive algorithm that we already know, and then just checking a table to say, like, OK, did I already do this? And in that case, return it, in the bottom-up version, I'm going to build up my array x i, j because so there's no recursion at all. So what would that look like?
So in the bottom-up case, notice that, in some sense, memoization is a top-down strategy. I would call it on n, comma n. Here, we're going to start from 0 and work our way toward n, right? So we'll start with x 0, j equals x i, 0 equals 0 for all i, j. Obviously, I can do this with a for loop.
And now, well, remember, if we think about our topological order, x i, j only depends on previous j's, right? So it makes sense to have an outer loop, which is over j. And now, inside of this outer loop, I can compute anything that I want in that j column of x. And I'm in good shape, because I'm building it up one column at a time, right?
So in particular, now we can do our loop over i and then just have x j and now evaluate our R step in our SRTBOT paradigm. And notice that that's perfectly fine. Because by the time I get to computing x i, j, I've already filled in x i, j minus 1, which is all I needed to evaluate that formula.
So what are the advantages and disadvantages here? So notice that here, our runtime is staring you in the phase. Right? We have n squared sub-problems, order 1 work, and you're done.
On the other hand, there's some possibility. If you were an old-school AI person, you might be able to do some pruning on your left-hand side that I can't do over here. Right? So here, I'm literally evaluating every entry of x i, j. It's not the case for this particular problem, but maybe x i, j only depends on x i, j minus 5. This strategy is going to still build up that whole table. This one, maybe you can skip over some entries. So in practice, it could help.
On the other hand, here, I've got a bunch of recursive calls I've put on the stack of my computer, that here, I don't have. So I think, actually, because of the overhead of recursion, typically, the strategy on the right is preferred; also, for clarity. But that's a blanket statement that I shouldn't make.
OK, so anyway, I think I've done this problem to death. Are there any questions here? I just thought I'd fill in for something I promised last time and didn't actually do. OK, fabulous. So we'll go on to problem 9-2.
So this is continuing from last time in the saga of Tim the Beaver here. So I forget what Tim the Beaver was doing in our last problem session. But today, Tim the Beaver is going to the career fair. And as we all know, the only real purpose of going to a career fair is to pick up free stuff. We joke.
Actually, you guys should all go to the career fair. I got my first job out of college by going to a career fair and haranguing somebody that picks our booth, until they let me in. But in any event, so Tim the Beaver is not interested in getting a job, but rather just wants swag, just wants free stuff out of booths at the career fair. OK?
So in this particular problem, there's n booths-- is it booths? It's certainly not boots. I don't know. There are n booths, each of which has a swag. And in particular, each swag has a value associated to it, which is Ci, which is the coolness of object i. It additionally has Wi. This is the weight of object i.
And just to make this problem verbally difficult to communicate, there's a W-A-I-T associated with each object, which is the time it takes to wait in line and pick up object i. OK? And Tim the Beaver, if there's some ridiculously cool object with a little wait time, he might just keep getting in line and getting more of that object. So unlike Ceal Naffrey on our first problem, Tim the Beaver is perfectly happy to have more than one of the same thing. OK.
In addition to this, just to make this problem, in my opinion, slightly more annoying and point-losing, Tim the Beaver also takes one minute to get in line at any booth. So we're just going to have to remember that when we account for our time ti. OK. So let's continue adding some more constants to our problem.
So each booth has an object which has coolness, Ci, weight Wi, time ti. Tim is carrying a bag. The bag can hold a particular weight. b. So this is the max weight that Tim can hold in his bag at any given time. And finally, Tim is a greedy beaver, but he also can go home, or go back to his dam, I suppose, and empty his bag. Right?
And so h is the amount of time to go home and back and empty his bag, in the meantime. And again, just to be annoying, don't forget he incurs plus 1 to get in the next line. This is what made my answer wrong, and I'm bitter. So I'm going to keep complaining about it. OK.
So of course, what he wants is the max. What would economists call this? I don't know. But for Tim the Beaver, he wants the max total coolness, or MTC, which is, of course, a number that we're all trying to optimize, in k minutes. You might remember those old TV shows, where you get one minute in a grocery store to empty the shells into your cart kind of thing.
So he wants to do this, and the computation time that he's reserved for this is an order nbk. And I know it's a big setup. I tried to document all the different constants that are in this problem. I don't think I missed any here. OK, fabulous. So incidentally, continuing on--
AUDIENCE: What's k?
JUSTIN SOLOMON: k-- k is the total amount of time that Tim the Beaver has allotted to do his job fair scavenging. Fabulous. Any other? Cool. OK. Notice that this is going to be an example of a problem that was not kosher in last week's problem session, in the sense that k is included in our runtime. Right? But what is k? It's just a number. k doesn't scale in the size of your problem in a linear way. So it's not going to matter for how we solve our problem But it is just a feature that's worth pointing out. OK.
So how do we solve dynamic programming problems? We use SRTBOT, or a Señor BST, if you are watching previous iterations of this course. OK, so right, so let's do that. See, I'm trying to conserve space. This is not going to end up succeeding.
So again, what are the different sub-problems here? Well, what are the different things that are limiting Tim the Beaver? What are his constraints? Well, he only has so much time.
And this is going to sound more philosophical in intent, but time always moves forward for Tim the Beaver. So this is a pretty good candidate in terms of dynamic programming. Because there's not going to be some cyclical dependency. Remember that in dynamic programming, we're all about trying to identify topological orderings in our sub-problems. And when you see something like time, not only does it also begin with t, but it's useful in the sense that time moves forward.
There's never a case where Tim the Beaver purchases a weird Warp Speed airplane and somehow goes back in time. That doesn't happen in this particular homework problem and for Tim the Beaver's for sake, I hope in no homework problems. So this is a long-winded way of saying that time is a pretty reasonable constraint to put in our problem, not a constraint as much as an index, I guess.
Moreover, there's another thing which is limiting Tim the Beaver, which is the capacity of his bag. Remember, he can only hold weight b. But this one should give you the heebie-jeebies a little bit. Because this problem, as a twist, has allowed the bag to empty itself out. So it's not true that somehow, you can come up with sub-problems where Tim the Beaver is just monotonically decreasing the weight of his bag.
However, if he does choose to decrease the weight of his bag, he has to spend time doing it. So time continues to move forward for Tim the Beaver. And that's what's going to give us our topological order. Yeah? This is a little too philosophical, I guess.
But in some sense, this is a long-winded way of saying, for our SRTBOT paradigm here, a totally reasonable thing to do would be to have x i, j-- I don't think, in this class, we do this definition notation. So just x i, j equals the max coolness where he has i minutes, and he has j weight-- let me glance at my answer to see if it's left in his bag or weight that he's carrying. Left in his bag-- either one would make a reasonable problem. I'm just bad at looking at my notes and seeing them disagree with the board. So I'm going to try and stay consistent.
OK, so x i, j is the MTC, but in i minutes with j weight left, rather than in k minutes with b weight, which is going to be our base problem. OK. Oh no, I did it out of order. Disregard that. We'll get to the b in a minute. OK.
OK, so that's our sub-problems. So now let's do the R in SRTBOT. [LAUGHING] I hate this classroom so much. I'm sorry. So right, so let's say that Tim the Beaver has i minutes left on the clock. And he has j weight in his bag. He's got a number of actions that he can take. Yeah? And let's think about what all those actions are.
By the way, there's one thing that plausibly, Tim the Beaver could do. But he doesn't really need to, is do nothing now, but then in 10 seconds, do something. You could account for that in this problem, but there's not a reason, right? He might as well stack up all his actions and then leave all of his leftover time at the end. You convince yourself that that's sort of OK. Tim prefers a compressed schedule. Right.
So he's got a lot of energy, this Beaver. That's what they say. They're nature's construction workers, something? OK. All right, so let's think about all of our options.
So for one, Tim the Beaver could not do a damn thing. He could just sit around for the rest of time, and that would be perfectly fine. So a different way of putting that is that he could give up. How much coolness would Tim get from giving up? 0. That's right, kids. Giving up makes you zero cool.
So OK, so Tim the Beaver is trying to maximize. He has a lot of different options. One of them is 0, meaning he gave up. Right? Notice he could recurse. Like, x-- I guess what would be-- he could give up for one minute, and have that be x i minus 1j, or something. I forget if I'm going to do i plus 1 or i minus 1. i minus 1j.
But there's no reason. He can just give up and stop. That would just be extra recursive cost for no good reason. OK. The next thing that he could do is he could get in line for a booth. Right?
So first, let's work out what happens then. So for one, he gets coolness. Let's say that he gets into booth k. So he gets coolness Ck when he does that. And now he can recurse.
So first, you have to account for the time. So it takes tk-- that's a t-- time. Or does it? No. Because it takes an extra minute for him to get in the next line, or to get in this line. I guess this is better.
And moreover, he needs to account for j minus Wk, like that. Can he always do this? No, he needs to have this much time remaining, and he needs this much time in his bag-- or this much weight remaining in his bag. You guys can work out the inequality. But since I only have a foot here, I'll just say, if applicable.
This is a great way to lose points on your homework. But for board-writing, it's OK. So when I say applicable, I mean these two numbers had better be greater than or equal to 0. OK? And you can do this for all k, right? So in other words, he can choose to get into booth k.
And Tim has the third option, which is he can go home. OK? So what happens if he goes home? Does Tim the Beaver get cooler when he goes home? No, I'm afraid to say. But he does spend time. So how much time does he spend? h--
AUDIENCE: He should have lost coolness.
JUSTIN SOLOMON: What was that?
AUDIENCE: He should have lost coolness.
JUSTIN SOLOMON: He should have lost coolness by going home. No, home is cool, guys, especially this semester. Stay home. Stay indoors. Right. So he loses home time h and 1 to get in the next line. OK?
But he's got a devil's bargain, of sorts. He loses time, but he gains bag, bagginess. Weight is the word that I'm looking for, b, like that. OK? Again, in this case, remember that he still needs, if i is greater than h. Otherwise, he's in trouble.
By the way, I wrote this in an annoying way. A different way of saying that is i minus h minus 1 is greater than or equal to 0, which is really what's applicable in every recursive call. But this is strict greater than [INAUDIBLE]. There's another little thing that caught me up, when I was reading the answer here.
OK, and those are all the options for Tim the Beaver, yeah? Notice that every one of our options either gives up completely or decreases time. So we have our topological order, which is, again, the arrow of time always continues to move forward. I'm going to prove that rigorously by putting a check mark next to the letter T. Again, on your homework, you should write out your answers.
What is our base case for Tim? Well, how much coolness do you get, if you have no time? 0 coolness. That's how much. So we have this expression, x0j equals 0 for all j.
Incidentally, although it's perfectly fine to have this be your base case, actually, in some sense, I didn't need it, because Tim the Beaver always had the option of giving up. So you could, I guess, in this problem, have no base case, if you really wanted. It would be OK, but kind of weird. OK.
And what's our original problem? Well, he starts out with time k and weight capacity b in his bag. So it's x k, b. And then finally, we need to do our runtime analysis.
So how many sub-problems are there? Well, again, a sub-problem is basically just the number of indexes for most of our dynamic programming problems. Right? So the first index is time. The second one is bag size. This is always between 0 and b. This is always between 0 and k. So there's order kb sub-problems.
How much time does each sub-problem take? Well, notice that I have to loop over all of my different options k here. So I incur-- oh, I'm noticing k is abused in our answer to this problem. We should use k only once.
OK, so here's where I made a mistake. And I believe it's in the written solution, but I'm not going to take now. There's k, which is the total time that Tim the Beaver has, and there's the k that I use as an index here. And those are not the same. I guess I can make this k bar really fast. There you go, problem solved.
And I just noticed that, because I was doing my runtime. And it's not order k. It's the loop over all the k bars. How many k bars are there? Well, these are all the different booths, and those are n of those. So this is order n time per sub-problem, which gives me a total runtime of order kbn, which, I believe-- oh-oh, our desired runtime was order nbk. But I think we can convince ourselves that indeed, those are the same thing.
OK, so my apologies for a slight overloaded character here. But honestly, it's one of those things, if you read the answer, you probably wouldn't even notice. But now that I'm saying it out loud, I am. OK. And that solves Tim the Beaver's maximization problem. He's a very cool Beaver. Any questions about this one?
Notice that both of these problems are very similar in nature. I basically just wrote sub-problems indexed by every possible thing and then enumerated every possible solution. I think this is totally sensible. Again, I remember I had a math Professor in college that always would use this phrase-- it's important not to think here. And I think this is absolutely true for these dynamic programming problems, that somehow they look a lot more complicated than they are. Fabulous.
So problem 3, protein parsing. Ah, yeah, so this one also got me tripped up for a minute. Because the runtime they want is not the runtime of the obvious solution, but it kind of, sort of is, after a little bit of fixing. OK.
So Professor Leric Ander has a laboratory. And that laboratory processes DNA. Ta-da. OK, so let me go to my notes here, because I think they're easier to read. So a strand of DNA, as we all know, because we're MIT students, is equal to basically a strand of characters that are ACTG.
If you ask me to name what those stood for, I could make a stab at one or two of them. But I'm a failure of-- I did not have the GIR, because I went to Stanford. And this is why I'm apparently a poorly educated person, according to a person in a faculty meeting.
But in any event, so we have a strand of DNA. It's basically a long string of characters that are one of four options. I'm told that there's sometimes a fifth and a sixth option, but not too often. In this problem, there's not.
And moreover, so a strand can be cut up. So I have this big, long strand, and I'm looking for certain markers. In particular, I have a list P of markers, which are really a sequence of less than or equal to k nucleotides. By the way, this really is something that people do. String searching really is applicable to processing these DNA strands.
Obviously, . I think, in practice, these techniques have to be a lot more resilient to error. But really, a lot of these algorithms we're covering are not all that far off from how people process these giant data sets, which is pretty cool, I think. OK, so what are we going to do?
We have a string. And then we're going to make a division. So we'll call our string S and our division D, which kind of looks like d1 to dm, which are substrings that concatenate to make the full guy. So if S is our input string, then a division D is just like chopping up S into little substrings, each of which we can give a name little d here. So big D is the full division. Little d is all the little pieces.
There's that old song about going through the big D in [INAUDIBLE], Dallas. I think it's for divorce. Right, OK, so the value of a division is the number of strands. So strands are these little d's here, that are in our list P. OK?
So given S and P, what we want is the max value. And the runtime that we're budgeted is kind of a weird runtime. And this should make us a little suspicious. So the max value is big O of-- I think I wrote it slightly differently in the problem, but whatever-- distributed to k, like that. So it's k mod P plus k squared. mod S. So we have two terms here, which somehow smells funny in dynamic programming. OK.
So what are we to do? Well, what I did is I ignored our desired runtime, came up with a dynamic program, and noticed that it was a little wrong, and then fixed it. By wrong, I mean it was correct. It just wasn't fast enough. OK, so let's do version 1 here-- 1.0, that makes it more accurate, or more precise. I always confuse those two.
So here's a thing that is going to be a little bit funny. Because it's going to look like we're going to have an easy computational problem. But then it's going to turn out that it's actually too slow.
So in particular, in our S in SRTBOT, what we could do is say, xi is going to be the max value of a suffix. If you're wondering, I don't know the difference between prefix and suffix. But I wrote the word suffix in my notes and checked it at home. Si, comma, colon-- see, it's suffix, because it starts at i, and it goes to the end.
JUSTIN SOLOMON: Huh?
AUDIENCE: No comma.
JUSTIN SOLOMON: In Matlab, it would be a comma. And that's a colon, and that's an i. OK. [LAUGHING] Thanks. I'm inventing my own programming language on the board as we go today. All right, so and notice this is a reasonable set of sub-problems, right? Because obviously, if I lop off some piece of my string, then the max value I can get is just the value of whatever remains.
So spiritually, this somehow feels like the right dynamic programming. And indeed, we'll see that it is. But it just requires a little bit of a list that could be to run on the right time. OK?
So let's do a recursive call. And this is actually straightforward. At least, the recursive call that you want to make is straightforward. And then we'll see that there is an equivalent formula, which is the one you'll see in the solution, which looks more complicated. But is the same thing.
So I'm going to write it as pseudocode for our recursive call, rather than as one giant formula, because I think it's easier to follow. Not pseudocode-- I know that's frowned upon in this class, a description of a set of steps for obtaining a recursive call, rather than a formula.
So in particular, we're going to initialize xi to be 0. We want to do a maximization problem. So initializing a variable to 0 is a sensible thing to do. And remember, what can we do here? So we're looking at a suffix. I could go down my list P of all the different markers, see if any of the matches the first couple of characters of my string. If it does, I get some value, and then I hop onto the next thing. Does that make sense?
Oops, I'm realizing I have a slight mistake here. Rather than initializing to 0, [LAUGHING] I actually have one additional option that I forgot to account for. Because I could just not use this character. I could put it in its own little snippet and get no value from it.
So maybe initially, I make a recursive call like that. It would be OK to initialize it to 0 and then do this-- but whatever. Notice we're already seeing the t in our SRTBOT start to stick out at us. We're going to only depend on bigger indices i. OK.
But in addition to this, this isn't enough, right? This would obviously just recurse the end of our list and do nothing. We get value if we can find a substring that's in our list. So what we could do is, for each marker in P-- remember P is the list of things that we're looking for-- OK, what could I do? If the marker matches S starting at i-- I'm going to just not even attempt to do Python-- starting at i and ending at the length of the marker, well, what could I do?
I could get $1 by matching that object. And then I have to hop forward the length of the string in my recursive call, right? So well, I could do that, or I could not do that. And I want to maximize, right? So I can keep my old value, or I could get one point by using this as my match, plus xi plus the length of the marker. OK?
This is actually, in my mind, the simplest possible dynamic program you could come up with. This is actually a totally fine dynamic program. We'll just see that the runtime isn't good enough. OK.
So does everybody agree this is a way I could solve this problem, and it would give me a correct answer? I'll do the TBOT.
JUSTIN SOLOMON: Yeah, that's what we're going to do. But we're going to maybe skip some parts of SRTBOT. So in particular, what's the topological order? Notice that I always look to the right, when I make a recursive call.
What's my base case? Well, in this case, it's just a x of 0, I guess, because I'm looking forward. Oh, I'm sorry. My base case is x of the whole length of the string, which is going to return 0, right? Because if I have no string, I can't get any value out of it.
The original is x of 0, meaning that I want the whole string. And let's actually do the runtime analysis, as Jason suggests, because that's, of course, the relevant computation here. So this is T2, because it's the second T in SRTBOT.
OK, so how many sub-problems are there? Well, I mean, it almost looks like we should have a fast algorithm. Because our sub-problems are only indexed by i, right? So there's one sub-problem for basically each character in the string. So there's mod S sub-problems total.
But how much time does it take for each sub-problem? , Well let's be careful. So there's a for loop over all the markers in P. So I know that I incur at least mod P work in my sub-problem. Is that all the work that I incur? It kind of looks like it, because there's only one for loop, and there's an if.
But how do I check if strings are equal? Well, I have to iterate over the length of the string. So I incur a second cost, which is checking if two strings are equal. We know that our strings are at most-- well, I didn't actually write it down. But the problem tells us that our strings are at most length k.
So I incur another factor of k in every one of my sub-problems. And that implies that our whole algorithm, that I've outlined for you all above, takes mod S k squared-- oh, I'm sorry. That was a lie. If I actually multiply these things together, what I get is-- sorry. [LAUGHING] I need more sleep-- S P k. And that is against the rules. So that's frowny face, yeah. Because in particular, I took two big numbers, in some sense, and multiplied them together, and that's not good.
So what is a person to do? I tried to solve my problem. I came up with-- by the way, from a partial credit perspective, I think you'd be doing OK if you got to this point. OK, not great, but OK. But of course, the problem is asking you to solve this is this funny runtime, which is kP, plus k squared S.
When I see a sum like this-- and remember that there are two problems to solve. There are two strategies for solving problems in algorithms class. There's one which is useful in your everyday lives, which is to devise algorithms. There's a second, which is to psychologically diagnose your instructors. And I think that second strategy is actually pretty effective here.
I see two terms. Most of our dynamic programming things involve filling in a table, where you would expect there to be a product. So in general, I would squint at this and think, like, hm, maybe I have to do some pre-computation. Yeah? In particular, we've got to do a lot of string-matching in our problem, and maybe we can make that more efficient. Yeah? That's sort of the main question here.
So this thing is too slow, and we're trying to fix it. The way I'm going to try and fix it is to say, like, OK, well, I have mod S sub-problems. If I look at these two terms, how much work can I actually do in my sub-problem, something that looks like k squared, maybe plus this amount of pre-computation. See what I did there? OK. So let's do that.
So in particular, here are the types of queries that I'm going to have to make. There's a bunch of times in my code when-- here's a number that-- [HICCUPS] yikes, I'm falling apart. That's what I get for sprinting across campus to get here. I'm going to find a number m i, j. And it's going to be 1 if the substring S i-- oh, man-- to j is in my list of markers P, and 0 otherwise.
By the way, why is this enough? Notice that I'm not answering, which marker? But the problem doesn't really care, right? This problem just checks if there is a marker. And if so, then I use that, yeah. So if I have this thing, that's going to somehow make this for loop a heck of a lot easier, because now I don't have to do string-matching. We'll return to that in a minute. OK.
So my question is, how can I compute this thing? And by the way, notice that I know how to compute this when the difference between i and j is bigger than k, because I know that all of my markers have length k or less. That's going to be important, because even though m is doubly indexed, I don't actually need to do that. In fact, I could even store it using less memory than that, if I wanted to, by just storing that diagonal block. OK, right.
So the other thing, which I think we saw in a previous problem session, as well, is that when we do a lot of string-matching, it often pays to put our strings into a hash table, so that they're easier to look up later. Does this string exist in this thing or not? Rather than matching every character every single time.
So maybe we do that, just for fun. And in general, when you see a string-matching problem-- and you have a list of strings, I'd suggest thinking about hash tables, just for fun and profit. So in step one here, maybe I put all the strings in P into a hash. OK?
How much time does that take? Well, I have to process every string, find its code, which is going to take more of k time. There's mod P of them, so this is k mod P time. Notice that that conveniently agrees with our first term here. So we feel like, aha, we're in good shape. We're making progress here.
And now, maybe I want to fill in this m, i, j objects here. How could I do that? Well, for one, I'm certainly going to iterate over all possible i's. OK? So let's do that. So we're going to do 2.
By the way, I'm using 1's and 2's and a's and b's and the whatever. These are just ways to denote steps of things. [LAUGHING] OK, so let's say that I just want to fill in m using a brain-dead algorithm. So I could go from 1 to the size of my strings for i.
Careful. Now I can't incur an S squared. But I know that my strings are always at mostly k. So I could do for j equals i plus 1 to i plus k. So this for loop actually incurs k time, not mod S time.
And now I can do two things. I can find the hash of the string from i to j. This is going to take order k time. And I can search for it-- ah, no-- to see if it's actually in our hash table of P. And if it is, then I set m equal to 1. Otherwise, I set m equal to 0. And these are constant time operations, at least in expectation.
So how much cost to it? And so this, you can convince yourself fills in that array m. How much time does it take? Well, I have a loop to the size of S. I have a loop of size k. I have a second loop of size k here to compute the hash.
So this whole thing is going to take order k squared mod S time, like that. Now, this conveniently-- there's chalk on the floor. And this is the second term in our runtime. So this is kosher. We can fill in m. And that's a convenient object to have around.
So the only thing that remains is to revise our R from above, to make use of the m that we have. And that's pretty straightforward. So the trick is to not lean against this thing and to actually hit the stop button. I'm learning. So now I suppose we had T2 for the second T. So for revised R, we should have R prime. [LAUGHING]
JUSTIN SOLOMON: What was that?
AUDIENCE: The deriviative.
JUSTIN SOLOMON: That's right. The derivative-- yeah, we could do Christoffel symbols, like i, j prime, and semicolon k. Take 6838 if you want to learn what Christoffel symbols are. Right, so now, what is my recursive call for xi? Well, I want to maximize.
Well, what can I do? I can check every possible length of a string that could be in P, check if it is, using my array m, and get that amount of profit. So in particular, I get m. By the way, I keep using the word profit here. I am essentially using that to mean increment in every single one of our problems here. I like to think of our problems as maximizing profit, because I'm a greedy professor.
So this is m, i j, which would be 1 if I found a string there and 0 if I'm not, plus xi plus j, to account for the length here, where j is in 1 to-- well, either the length of the string, or I get to the end of-- either the maximum length of a string in P, or I get to the end of my array, like that. OK.
And this is our new recursive call. The one thing we should double-check is, what is the runtime for actually filling in x now? Well, there's still the mod S sub-problems. And now how long does it take? Well, now I just have one loop over k things. This is mod S times k. It's actually less than any of the terms that's in our runtime.
And so this is fine. This actually is kind of a funny example, where the dynamic programming part of our algorithm, once we've done all this cute pre-computation, is actually insignificant, compared to all the pre-computation that we had to do in our final runtime-- sneaky. All right, any questions about protein folding, or whatever it is that we just did? OK. So as usual, I'm talking too much.
JUSTIN SOLOMON: Yeah, which one would you prefer to cut?
JUSTIN SOLOMON: I have very few preferences. OK, so one of your problems-- I would take a vote, but with our audience, there's is a high probability of a split on jury here. Right. So there's two remaining problems on the problem session. As usual, your instructor--
JUSTIN SOLOMON: You can leave it there. This is another problem to learn about. As usual, I've talked too much and haven't got to the end. I get the impression that in 6006, this egg drop thing is a bit of a tradition anyway. So maybe we'll cover that problem really fast. Do they do that in section, some variation?
JUSTIN SOLOMON: Not this time-- even better. OK, so yes, so maybe we'll do this egg-drop thing, mostly because the other one, I think, takes a lot of verbal setup. The other one is-- I would say, from a dynamic programming perspective, maybe not super exciting. But from an interesting problem perspective, it's kind of cool to think about. So I'd encourage you to leave it in there, and you guys can read it at home.
From a coding perspective, it's also kind of fun. I notice the solution didn't do what I would do, which would be to use the bits in the-- assume that something wasn't too tall, use the bits in an integer to store your binary variables. But that's an old hack. That's like this old hack for computing the square root of a number, that's apparently in the code for the Doom video game, which involves bit-shifting, and it happens to agree with square root, for some magic reason, that numerical analysts really hate.
OK, so let's do lazy egg drop instead. So that's problem 4. OK. So we're in a building. Our building has n floors and k eggs. I guess it's debatable whether the building has eggs or the residents-- but in any event, have some set of eggs. And maybe I'm in this data center or some other weird building. So I don't have heights of floors that are isotropic, but rather, each floor has a different height, which could vary.
So it's height of floor, or i. I really want to write flour, but I digress. And we're going to assume that our list is already sorted. So in other words, the fifth floor is taller than the fourth floor, and we don't have to spend n log-in time doing that. OK. Right.
So apparently, in our problem, we have an egg with a mysterious mechanical property that we are trying to recover. And all eggs, as we know, are identical. So the only difference between eggs is chicken, versus goose, versus-- I'm struggling to think of a third category of poultry-- turkey. Thank you.
But assuming that I got all of my eggs at the same Stop-N-Shop, and they all come from the same species, then they have roughly the same mechanical properties. Actually, probably the better setup is that floors are very far apart relative to the size of an egg. And if I get high enough, my egg, when I drop them on the ground, like that, breaks. Didn't break. But it could have broken.
And of course, if I drop it from an even higher height, my egg still is going to break. However, if I have a very low floor-- apparently, a very low floor. Maybe this is a house for mice. And I drop my egg, it actually stays intact. And the question, as all good scientists want to know, is, what is the highest floor in my building from which I can drop an egg and have it remain intact?
And the question is kind of a funny one. It's sort of like experimental design, in some sense. It's not asking, given this and a list of experiments, try and figure, infer, something about the eggs. But rather, it's saying, if I carefully design a sequence of floors to drop my eggs from, from which upon I drop my eggs, then what is the maximum number of experiments I need to do to triangulate in on that floor, that critical floor, above which my eggs break?
So what I'm given are the heights of the floors and a bunch of eggs. In some sense, the budget of eggs doesn't matter more than just putting a cap on the size of our problem, in some sense. What really matters is I'd like to use fewer than k eggs to determine that. Because of course, the remaining ones I'm going to use to make an omelet.
But notice that I can be a little sneaky in my experimental design, that what happens if I drop my egg from a really low floor in my building? Well, it remains intact. So I can schlep down the stairs. I can pick up my egg, and I can use it for my next experiment. And I have not paid an egg. Yeah?
So the first question you might ask is, like, well, why the heck wouldn't I just start on the first floor, drop the egg. If it's not broken, go on to the next one and then drop the egg, and so on? That would be the most egg-efficient plan. And indeed that is the case, because you'll only break at most one egg.
But you're schlepping up and down the stairs a bunch of times when you solve that, right? Every single time, you've got to go retrieve that unbroken egg. You've got to run down the stairs, pick the thing up, and then run up to the next floor.
And maybe in your optimization problem, rather than trying to minimize the number of eggs that you break, you're trying to minimize the expense on your quads. And so instead, you've skipped your leg day, or whatever, and the thing that you're trying to minimize is the sum over the heights of the drops in your experiments. So you're trying to determine the mechanical property of your egg by designing an experiment, sort of a procedure, that minimizes the number of times that you need to drop eggs. Because every time you do, you've got to run all the way back down the stairs, and go look at the pavement, and see if the egg broke or not. That's a lot of work. OK.
This is different from the classic egg-drop 6006 problem, which I encourage you guys to go seek out, in previous iterations of this course. Let me see. And so the question is, what is the minimum number of egg drops you need to do to ascertain that for any type of egg-- so I give you a mystery basket of eggs, and you have to design the experimental procedure and bound the number of this particular value here, given a budget of k eggs. OK.
And the amount of time that we have to do that is order n cubed k. Apparently, our building, we have lots of eggs and not very many floors. OK. Does our setup make some sense here? We're just trying to avoid running up and down the stairs. That's the main takeaway. OK.
So what are we going to do? SRTBOT, because that's all we know how to do, yeah? And in particular, we're going to make one observation, which is kind of handy. If I drop a floor-- ooh, if I drop an egg from a floor, in this deterministic universe, where egg mechanics are very predictable, there's only one of two things that can happen. Either the egg broke or it didn't, while I run into the board again.
So let's think about our experiment. Remember, at the end of the day, we're trying to figure out the tallest floor in my building from which I can safely drop an egg. So if I think about bracketing that height of that floor, for one thing, do I ever need a bracket that's not a continuous or a connected set of numbers? The answer is no, right?
It should never be the case that, like, oh, I think that my eggs could be on floors one-- only the prime number of floors in my building, or something. That really makes no sense, right? Because if I convince myself my egg breaks at four or five, then obviously, floors six through n, my egg also breaks. And so I always can just keep narrowing down some interval. Right?
So in particular, here's a clever S in my SRTBOT, which is to say that I'm going to say that x, i, j, e is equal to the minimum-- by the way, I'm writing this as minimum total height. So this is the minimum total times I've got to run down the stairs and check my eggs, or total height that I run down the stair-- the number of stairs I run down, assuming my stairs are 1 foot tall-- where I have e eggs left.
Notice the way that we've written the problem this time, I might as well use all of my k eggs. That doesn't cost me anything. What costs me is running up and down the stairs. And that I have floors i through j inclusive to check. So in other words, if I'm on a floor below floor i, I've convinced myself my egg won't break. But if I'm on a a floor above floor j, I'm convinced my egg will break. OK?
So what do I do? Well, remember that this is an experimental design problem. I can drop my egg from any floor f, which is in the range i to j. And of course, there's never a reason for me to drop an egg from a floor below i or above j, because we already know what happens in that case. OK?
So what happens when we do that? Well, if I drop it from floor f, I have to pay, in terms of my cost function, right? Because to pay the height of f, I've got run down the stairs. OK? But in exchange for that, I learn a little bit about my egg problem. I either get an upper or a lower bound of f, depending on whether the egg broke. OK?
So let's formalize that mathematically. So in particular, we have x i, j, e. Well, what do I get to control in my life? And what do I have to deal with? Well, what I have to deal with is the fact that I don't know what's going to happen to the egg. It might break. It might not. Right?
And the egg might be an adversarial egg-- it wants you to run up and down the stairs. And I have to account for that. But i and the egg's adversary need to choose what floor I drop it from. So remember, we saw an example. I forget what, from class, where there was a game. One guy was trying to minimize. The other was trying to maximize.
In some sense, the egg is trying to maximize the amount of work you have to do, running up and down the stairs to do your experiment. A better way to put it is that we're trying to upper bound the amount of work in your experimental procedure. And I'm trying to design a procedure that minimizes my work.
So let's say that I'm the player. So I want to minimize. And the decision that I get to make, the control that I have, is what? Well, it's what floor I choose. So let's say I choose floor f. Well, I have to go down the stairs. So that takes me hf. This was going up the stairs, is probably what incurs the hf going down is nothing. But I digress.
But now I still am not done. I've narrowed it down into one of two cases, right? Either f is my new lower bound or my new upper bound. And I have to account for both of those in my recursion and actually, the max of those two, in the sense that I need, in every possible case, that my egg drop experiment narrows down my floor to a width of 0.
So in particular, this is a mini-max problem. There's a max inside of of a min here. So either the egg broke in my experiment or it didn't. Right? So if it did broke, then, well, what happened? Well, let's see here. If the egg broke, then I got an upper bound for my floor. So my lower bound remains the same. It's i. My upper bound is f minus 1, because it broke on floor f.
Well, what happens to eggs when they break? I can't drop them from floors again. So I lost an egg. OK? So this is my egg broke. OK? Or my egg didn't break. So in that case, well, if my egg did break, now I have a lower bound. So I'm only unclear about floors f plus 1. But the upper bound didn't change. It's still j.
And how many eggs do I have? Well, my egg didn't break, so I can run down the stairs, which is going to be tiring, I've accounted for that here. But at least I can re-use my egg. So I didn't lose anything. OK?
And I get to choose. So notice-- oops-- so do you guys see why there's a max here? Essentially, I have to account for every possible scenario when I'm designing my experimental procedure. But I get to minimize, in the sense that I can choose what floor at every step. So in particular, my f here is in the range i to j, like that.
OK, and now we have our recursive formula for our egg drop that minimizes total height. So now let's finish off SRTBOT in four minutes. It's actually not too hard. So I think we'll actually make it for once by removing 20% of the problems I was supposed to cover. All right.
So first of all, what's our topological order? So this one seems kind of annoying. Because I think usually, we think of spending stuff in a lot of these dynamic programming problems. But do we actually spend an egg? Not necessarily, right? Because in this recursive call, the number of eggs I had remained the same.
So maybe that's not actually a great way to establish a topological order. But instead, what do we know? What is in science the purpose of an experiment? It's to improve our understanding of the world. In this case, our world consists only of eggs and floors of buildings.
And in particular, once I drop that egg, I learn something about my building. And I narrowed down the range of floors that are uncertain for me. So in particular, I know that x i, j, e only depends on x, I guess, i prime, j prime, e prime, with what? Well, my sub-problems, I always have a smaller range of flaws than I did before.
So in particular, j prime minus i prime is going to be smaller strictly than j minus i. And that'll give me my topological order. Cool. And so that's actually, I think, the sort of annoying part, other than working out this mini-max expression here. The remaining things are not so hard.
So what are our base cases? Well, let's say I have 0 eggs left. But I still have a set of uncertain floors. That's bad news. Yeah? Yeah, so that should be infinity. And the reason is, of course, I'm going to take the min here. Right? And so obviously, I should never choose infinity as a min.
So in other words, I should never choose an option for a floor that could possibly lead me to an uncertain scenario, when I run out of eggs, yeah? In addition to that, there's another base case here. This is I've got no eggs left, but some floors to check, is the second one, i minus 1e. So in this case, I've got e eggs left, and I'm done, right? I've narrowed it down to the bounds.
Incidentally, the way I wrote it in terms of inclusive, versus exclusive, might be a little fishy. Over here, you guys shouldn't be off by 1, like your instructor often is. But in any event, here, you're 0, right? There's no more floors left to check. You've narrowed it down to a range of 1. Again, something that I'm out of time so I'm not going to check carefully, is if my bounds are inclusive, should that be i, j minus 1-- i minus 1 or just i, i? But I think you guys are all smart enough to work that out at home,.
Which would my original case be, well, now I have all the floors to check, and all my eggs in my metaphorical basket here. So I have floors 1 to n here. And the problem tells me I have k eggs when I start. And then finally, I need to do my sub-problems here.
I think you can actually simplify the argument that's written down a tiny bit. And just again, look at your sub-problems. They're indexed by three numbers, I'm going to do a really conservative estimate. I think the problem actually works out a better estimate, but then asymptotically, it's the same.
What's our bound on the first and second index? Well, they're both just the index of floors, which go between 0 and n. Yeah? Obviously, you could do better than that, because the lower floor is always less than the upper floor, which is what the problem accounts for. But if I'm being lazy, then, well, there's n squared sub-problems to account for the two floors.
And the third index is your eggs, which you have at most k of. OK, how much work do we have per sub-problem? Well, let's see here. There's a for loop over f. f Is over floors. Again, if I'm going to be really conservative, well, there's at most n floors total in my building. And so that leads us to a runtime and n cubed k, which is what we wanted, at the end of the day. OK?
This should be a big O here, because I think technically, this is n plus 1, to account for floor zero. OK. And that solves our egg-drop experiment. I think this is a nice one. And I think, in my mind, actually, in terms of dynamic programming, this is one of the harder things to get right, which are these mini-max games.
I'd have to think about it, which in my negative two minutes, I'm not going to have time to do. I think in lecture, the way that we solve minimize problem was we separated out the min and the max, and we thought of there being two dynamic programming problems that are interacting with each other. You could probably write this one in that form, as well, I guess, just by pulling this term out and thinking of it as a different array.
But this form is perfectly fine, too. Either one's all right. But in my mind, these are the hardest things to get right in dynamic programming. So I would choose whichever one jives in your brain.
So in your thing, should we choose to leave it alone? There is a fifth problem here, which, as usual, I haven't managed to get to, where you're building walls by placing tiles. This is an interesting one, because your runtime is exponential. But the problem tells you that that's allowed.
But there's some exponential things which are OK and some that are not. Essentially, what you don't want is the product of two giant exponentials. You'd like to just get it down to one.
AUDIENCE: It's basically saying it's going to be polynomial [INAUDIBLE] small [INAUDIBLE].
JUSTIN SOLOMON: That's right. Or it's polynomial in everything except for the things it's exponential in. And moreover, the things it's exponential in are small. And the problem says that. So I encourage you guys to take a look. Because it really does take some time to logic through it. But the setup for that problem is, I think, longer than my glacially slow board-writing can handle. But with that, we'll call it for the day.