Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges

Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges


Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges

Learn how to use Dynamic Programming in this course for beginners. It can help you solve complex programming problems, such as those often seen in programming interview questions about data structures and algorithms.

This course was developed by Alvin Zablan from Coderbyte. Coderbyte is one of the top websites for technical interview prep and coding challenges.

🔗 Check out the Coderbyte channel:    / @coderbytedevelopers  
🔗 Improve your coding and interview skills: https://coderbyte.com/member?promo=ja… (NOT an affiliate link)

This course uses images and animations to help you visualize problems and important concepts. After understanding problems conceptually, you will learn how to solve them in JavaScript using Dynamic Programming. Even though JavaScript is used in this course, you will learn concepts and knowledge that you can apply to other programming languages.

⭐️ Course Contents ⭐️
⌨️ (00:00:00) course introduction
⌨️ (00:03:30) fib memoization
⌨️ (00:38:39) gridTraveler memoization
⌨️ (01:04:52) memoization recipe
⌨️ (01:09:56) canSum memoization
⌨️ (01:29:29) howSum memoization
⌨️ (01:52:06) bestSum memoization
⌨️ (02:12:45) canConstruct memoization
⌨️ (02:38:36) countConstruct memoization
⌨️ (02:47:30) allConstruct memoization
⌨️ (03:10:53) fib tabulation
⌨️ (03:22:17) gridTraveler tabulation
⌨️ (03:34:32) tabulation recipe
⌨️ (03:37:59) canSum tabulation
⌨️ (03:53:01) howSum tabulation
⌨️ (04:07:21) bestSum tabulation
⌨️ (04:20:50) canConstruct tabulation
⌨️ (04:38:06) countConstruct tabulation
⌨️ (04:50:23) allConstruct tabulation
⌨️ (05:07:44) closing thoughts

⭐️ Special thanks to our Champion supporters! ⭐️
🏆 Loc Do
🏆 Joseph C
🏆 DeezMaster



Learn to code for free and get a developer job: https://www.freecodecamp.org

Read hundreds of articles on programming: https://freecodecamp.org/news


Content

3.689 -> Hey programmers, I'm Alvin l come to our course on dynamic programming. So dynamic programming
8.57 -> is one of my most favorite topics to teach. But unfortunately, I feel like dynamic programming
12.599 -> also has a reputation for being a very difficult topic. That being said, I think dynamic programming
18.49 -> can be very intuitive. If we actually take a nice gradual progression through the material,
23.8 -> right? A lot of students have this habit of just trying to attempt a pretty hard dynamic
28.58 -> programming problems, without going through the necessary steps of really understanding
32.189 -> the material, right? It goes without saying, if you want to do well on those data structure
35.629 -> and algorithm interviews, you definitely need to know dynamic programming. So I hope to
39.409 -> give you all the background knowledge, you need to really crush those types of problems.
43.13 -> Now, that being said, What problems are we going to solve throughout the course? Here
47.05 -> are a few examples. So one question I can ask you is to calculate the fourth number
50.52 -> on the Fibonacci sequence seems like a very easy problem, can also ask you to count the
54.769 -> number of different ways to move through a six by nine grid, or something different,
58.92 -> like given a set of coins? How can we make 27 cents in the least number of coins? A final
63.789 -> example would be given a set of substrings? What are the possible ways to construct a
68.07 -> string potent pot? And all of these questions really fall under the umbrella of dynamic
74.03 -> programming. And this is really why I think the topic has such a bad reputation or being
78.3 -> very difficult, because the problems ranged so much, right? It looks like these problems
83.36 -> are totally different. And there may not be any underlying mechanics that we can use to
88.25 -> tackle all of them. But the short answer is, we really can if we have the correct way of
92.52 -> thinking about these problems. That being said, let's go over the overall format of
97.16 -> this course, in this course, I think our key to victory is going to be to really visualize
101.96 -> all of these algorithms, right? So we're going to spend a lot of time drawing things on the
105.95 -> whiteboard, as well as visualizing things with animations. To me all the heavy lifting
110.93 -> on an algorithm interview is really done when you come up with that picture, right? When
114.29 -> you describe that process, and then translating it into some code is really the easy part.
118.98 -> Right? The hard part is designing the algorithm in the first place, right? So we're going
122.19 -> to draw out of things to make sure we understand the structure of the problem, as well as coming
127.03 -> up with a solution. And then once we're really happy with that pen and paper process, then
131.64 -> we'll hop into my editor, and we'll solve it in some code, we'll probably have to go
136.25 -> back and forth, until we end up with an algorithm that runs in an efficient amount of time,
141.04 -> right. So it goes without saying, we're also going to analyze the time and space complexity
145.18 -> of all of our solutions. I'll be writing my code in JavaScript, but you'll find it very
149.15 -> easy to translate our solutions into the language of your choice. So in this course, on dynamic
154.98 -> programming, we're going to divide the material into two main parts. Part one is going to
158.84 -> be about memoization. And then Part two is going to be about tabulation. And don't sweat
163.37 -> it if you have no idea what those two terms refer to, don't worry, we'll reach all of
167.771 -> that material step by step together, I think you're going to realize if we actually learn
172.28 -> these things in all very logical progression, we're almost like discovering these algorithms.
176.53 -> And I don't just have to tell us what the algorithms are. So in terms of prerequisites,
180.29 -> I won't assume you know anything about dynamic programming, but I will assume that you understand
185.04 -> a little basic recursion, as well as some basics of complexity analysis, right. So you're
190.52 -> sort of familiar with big O notation. And I'm sure that we'll be able to review some
194.49 -> of that notation as we move along. So I think with all that out of the way, and no further
200.459 -> ado, let's hop into the course. Alright, so where should we start? Well, I want us to
205.849 -> really ease into this new topic. And so what we'll do is we'll start by attacking a problem
209.68 -> that you probably seen in the past that is I want to solve a Fibonacci problem. And so
213.86 -> for us, we'll have a particular phrasing of the Fibonacci problem. What I want to do is
218.06 -> write a function fib of n that takes in a number as an argument, and I need to return
221.989 -> the nth number of the Fibonacci sequence. And just to review, how does the sequence
226.35 -> work? Well, the first and second number of the sequence is just one. And then at any
230.02 -> point in time to generate the next number of the sequence, you can just sum the previous
233.42 -> two. So for example, these are the first few numbers of the Fibonacci sequence, right starts
239.23 -> 112358, and so on. What I'm saying is your number needs to take in like a position of
243.76 -> the sequence. In other words, if I asked you for the seventh Fibonacci number, you need
247.831 -> to return the answer 13, right? Because the seventh bonacci number is 13. And how do we
252.84 -> actually calculate that 13? Well, logically, it's just a sum of the previous two elements,
257.18 -> that is five plus eight gives me the 13. So very, very classic function here. And to really
263.12 -> get us going for what we do later on in the lesson, I'm going to require us to solve this
267.05 -> one recursively, right, it's really going to be at the heart of a topic for today. Why
270.87 -> don't we kick things off by quickly implementing the classic recursive implementation of a
276.22 -> Fibonacci function, probably in this function a few times in your programming career, usually
280.71 -> one of the earliest examples of recursion that we face. So we'll just lay out the classical
285.1 -> notation here. So I want to take in a number and and I want to return the number of the
289.48 -> Fibonacci sequence. Like we expect, the base case is just about, you know, the first two
293.63 -> numbers of the sequence. In other words, if I'm given some n, that's less than or equal
297.669 -> to two, then what I should do is just returned one. And I'm reading this because hey, the
302.88 -> first two numbers of Fibonacci sequence are exactly one. But in the recursive case, in
307.75 -> general, what I can do is just return the sum of the Fibonacci number right before the
312.69 -> one I'm asking for, as well as the Fibonacci number two before the one I'm asking for,
316.84 -> right? Again, the kind of baked in recursive nature Fibonacci is to calculate some Fibonacci
322.4 -> number, you can take the sum of the previous two numbers in the sequence right. Now, of
326.48 -> course, we should test our code for some correctness, what I'll do is call a few examples of this
331.02 -> fib function. So I'll try fib of six, seven, and eight. And I shouldn't get the answers
337.22 -> of 813 and 21, respectively, right. So let's give this a go. I'll run this in some JavaScript.
344.01 -> There we have it right. 813 and 21. So this is a very, very classic implementation of
349.199 -> Fibonacci, you've probably seen this many times before. And we did solve it recursively.
353.1 -> And really, what I want to do is give some a larger number to this fib function. So what
357.851 -> if I asked for, let's say, I don't know, the 15th Fibonacci numbers seems like something
362.28 -> reasonable to ask for. So if I give this code a shot, it looks like the first three calls
367.4 -> of bonacci do work fine, right, I get 813 and 21. But the fourth call is actually still
373.78 -> running, right, my program actually hasn't finished. And this is a big issue with this
377.63 -> type of implementation with Fibonacci. So obviously, this Fibonacci function needs some
381.76 -> work. Let's go ahead and head to the drawing board. Alright, so it's apparent that our
385.93 -> Fibonacci function is correct. And that returns a proper result. However, if we give it a
390.48 -> large enough value of n, it's pretty slow. That is our function has correctness but lacks
395.12 -> some efficiency. In the long run, we definitely want to improve this implementation of a recursive
399.28 -> Fibonacci. But to do that, it's really important that we identify exactly where there's even
403.69 -> room for improvement. And to do that, I think we should draw some things. This is something
407.84 -> I think students actually need to work on. Students have this habit of trying to like
411.37 -> picture everything in their mind. And that works for some easier problems. However, when
415.81 -> we want to tackle more complex problems, if we just try to capture all this information,
419.99 -> just mentally without drawing it on, like pen and paper, or marker and whiteboard, or
423.93 -> chalk and chalkboard, you're going to really lose track of the finer details in these structures.
428.259 -> So I want us to be very, very methodical, and we're going to draw really how you should
431.87 -> visualize a problem like Fibonacci. And so over to my drawing, let's say that I wanted
436.78 -> to trace through what happens when we call a fib with the number seven. That is I'm asking
440.63 -> for the seventh number in the Fibonacci sequence, I know that in the long run, I should get
444.96 -> back to 13. Right. 13 is the seventh number in the sequence. So I'll keep that goal in
449.73 -> mind. But over to my drawing, let's say I called fib of seven, I'll denote that by really
454.37 -> drawing a circle with my value for n inside. So I think about this call to fib of seven,
460.5 -> what is it going to do? Well, I know that seven is not the base case, right? Seven is
463.97 -> not less than or equal to two. So this call is going to branch out into some more recursive
468.919 -> calls. In particular, on its left hand side, I'm going to call n minus one, which is six.
472.61 -> On the right hand side, I'm going to do n minus two, which is five.
476.58 -> And at this point, I carry over the same logic for the other nodes have the structure, right,
481.68 -> if I look at the six node, if I'm routed right there, then that will have a left child of
485.81 -> minus one, so five, it will also have a right child of minus two. So four, you can start
490.96 -> to see a pattern where really this recursive structure just visualizes as a tree, which
495.69 -> is really neat. I hop over to this five node, it will have some children, right, minus one
500.13 -> on slept minus two on its right. And we will actually just continue this process for most
504.31 -> of these nodes. But let's say we pause right here. So you'll notice that these notes that
509.76 -> have pointed to in red, they're actually the base case, right? For those nodes, I have
514.24 -> values of two or one. And I know that those function calls will return immediately. More
519.25 -> importantly, that means they don't branch into any further calls. So I don't want to
523.57 -> start flushing out the tree of those nodes. Instead, I look at things that are not the
527.85 -> base case, right? That is these nodes in yellow. So I'll continue to flush out this tree, but
532.74 -> not branch out further for the base cases.
538.7 -> So at this point, I built out my entire tree, and I stopped fleshing out the tree whenever
542.75 -> we had a base case scenario. So this is actually the full recursive tree. Remember that the
547.95 -> numbers inside of the nodes here represent the end that we passed in. That being said,
552.03 -> if we have this visualization, how does this tree really calculate the Fibonacci answer?
557.14 -> Right, so let's start to break it down over here. Let's say I looked at some note, in
561.39 -> particular, this base case note of two, right? I know that this note is a base case, so it's
565.7 -> going to return the value of one according to my base case, when we say return, that
569.899 -> really just means return to your caller right return to your parent. So this note of two
574.23 -> is going to return one to its parent of three. In the same way, this node on the right hand
579.19 -> side of one is also a base case, two will return one, both of those values that are
584.34 -> returning, they go back to the parent of three, and three is actually going to add those two
588.5 -> values up. One plus one is two. And this makes a lot of sense because we know that the third
593.6 -> Fibonacci number is two. So we can continue this process. Okay, this node over here. This
599.2 -> is also a base case. So it returns one. And now the parent node of four is going to sum
604.56 -> up both of its children values. Two plus one is three. And that makes sense in itself,
609.8 -> because the fourth Fibonacci number is three. So you probably got the picture. Now let's
614.39 -> speed things up. For all of these base cases, I know that they're going to return one to
618.82 -> their parent. And for all parent nodes that have both of their children ready, that is
622.99 -> both of their children returned, they're just going to add up those values. And this process
627.48 -> just continues all the way up the tree, right? Just adding our left and right children. To
633.23 -> get the answer that we should return the very top of our tree at the root of our tree, we
637.17 -> get the final result of 13, which makes a lot of sense, because at the start, we said
641.41 -> that, hey, the seventh Fibonacci number is indeed 13. So now that we have a robust understanding
646.89 -> of how to visualize this fib function, what do we actually know about its speed? What
650.67 -> do we know about time complexity. And so you may have heard offhand people mentioned that
654.61 -> a classic recursive implementation of fib is going to be two to the n in time complexity.
659.55 -> And that is the case however, you really understand the reason why. So hidden in this picture
664.5 -> is the reason why Fibonacci for us is going to be two to the n in terms of its time complexity.
669.27 -> However, something that's kind of unfortunate about this drawing is it's asymmetric. And
673.33 -> that's, I think, a big reason why students have a really hard time convincing themselves
677.519 -> that a function like this has a two to the n power time complexity. So here's what we'll
681.5 -> do, why don't we warm up and kind of go through some basic understanding of time complexity?
685.61 -> And I promise we'll answer that Fibonacci question. So let's do a little warm up. Let's
690.17 -> say that I gave you this foo function, notice that it's different from our fib function,
694.67 -> right? It's similar in that recursive, this function is kind of arbitrary, it actually
698.67 -> doesn't calculate or solve any particular problem. So if I wanted to visualize how this
703.779 -> foo function behaves, let's draw it out. Let's say I initially call it the top level foo
708.42 -> of five. I know five is not a base case. So it's going to call upon n minus one, or it's
712.72 -> going to call upon for four calls three, three calls to two calls one, and then here, we've
718.31 -> actually bottomed out at a base case. If you look at the number of calls I made, I basically
722.399 -> made exactly five function calls. Which makes sense, because in terms of our base case,
727.22 -> where do we stop once we hit a number less than or equal to one and every recursive step,
731.579 -> we just subtract one from our current value of n. So overall, I have five calls here.
736.66 -> But if I generalize that, for any arbitrary input, I know that in the long run, I'm going
740.49 -> to have about n different function calls recursively. And so for that reason, the time complexity
745.29 -> of this is really just O of n time, right? I have to evaluate O of n different function
749.93 -> calls. While we're at it, why don't we take a look at the space complexity? Well, you
753.2 -> may have heard in past that when we analyze the space complexity of our recursive functions,
757.63 -> we should include any of the additional stack space that our function calls take up right,
761.931 -> when we make a recursive call, we add that to the call stack, and those must be tracked
766.07 -> by our computer. And so since we have about five or n different function calls added to
771.17 -> the stack, before we hit our base case, you can see that the space complexity of this
775.02 -> code is also O of n space, overall, we're looking at O of n time and open space for
779.31 -> this function. Pretty straightforward stuff, right? Let's look at another more involved
784.709 -> function. So let's say I gave you now this bar function, it's another arbitrary function,
789.76 -> it's very, very similar to foo, what you should notice is, the only difference is when we
793.839 -> make a recursive call, we do an n minus two instead of an n minus one. So how does that
798.73 -> actually change the time complexity of this function? So let's say I wanted to trace through
802.69 -> this and I made a top level call to bar of six, I know six is gonna call upon four four
807.85 -> is gonna call upon to, to call zero and zero actually hits a base case. So this is very
812.839 -> similar to our last example, except we kind of see that from one call to the next, we
816.85 -> kind of take a bigger step in time, right. And so in a way, we can say that we're moving
821.45 -> twice as far upon every recursive call. And this would actually half the number of recursive
825.7 -> calls we need overall. So I guess we might be tempted to say that the time complexity
829.779 -> of this one is N over two time, but a keen observer would note that according to our
834.26 -> bego, you know, understanding, we can remove any multiplicative constants when we have
838.68 -> a time complexity, so N over two is the same as one half times n. So this simplifies nicely,
843.54 -> it's just an O of n time complexity. Using the same exact argument, we can also say that
848.46 -> the space complexity from the stack is also open space. All right, so let's take a lay
854.29 -> of the land, I showed you two functions that are very similar, they really only differ
857.959 -> in how they made their recursive calls, right? One did minus one, the other did minus two.
862.1 -> But in the grand scheme of things, we saw that they had an identical complexity class,
865.78 -> right? We have O of n time and O of n space for both of these functions. So after these
870.06 -> two examples, you may be able to see the reason I wanted to bring them up, right, maybe you're
873.79 -> actually ready to make the logical leap and make some conclusion about our classic Fibonacci
878.2 -> recursive function. That being said, I don't want to skip any steps. I want to be super
882.43 -> methodical, I think, if we pay the cost of understanding fib right now, and I mean, truly,
887.05 -> like Absolutely. Understanding fib. It's really going to pay off later on in the lesson when
890.839 -> I slam me with some much harder problems. So let's be nice and methodical over here.
895.56 -> Let's take a look at some other functions. Let's say I gave you this did function now.
899.25 -> very particular Right, we'll pay it no mind. What do we notice about this did function?
903.3 -> Well, it has two recursive calls now right inside of every single call. And they both
908.029 -> do an n minus one. How should we visualize this one? Well, it's kind of similar to I
912.329 -> guess, our initial fib drawing and shape, where if we start with some initial call,
915.59 -> let's say five, five is going to branch to exactly two children, right? Because five
919.26 -> is not yet the base case. And for this did function, it does a minus one on its left,
923.81 -> and also it's a right child, right. So the next level is just for next levels, just threes
928.45 -> than twos, then just ones which would actually hit our base case over here. This is a really
933.41 -> nice, like, beautifully symmetric tree, right? Okay, so this is a visualization for our dib
938.19 -> function. But what does it tell us about the time complexity of it? Something you'll hear
942 -> me say a lot in this lesson is when we tackle a quote, unquote, new problem, or a new pattern
946.51 -> we're really trying to do is just leverage our past experience, right? So when I look
949.99 -> at this tree structure, I'm trying to notice anything familiar, right? Is there some strand
954.74 -> over here that I can grasp that to really feel comfortable and kind of extend my previous
959.089 -> learnings, right? Where can I find our base inside of this drawing? Boop, right here.
965.99 -> So if I look at this path, I've highlighted in yellow, it's really just the path starting
969.41 -> at the root node going down to some base case here, I just designated the leftmost path.
974.5 -> And what's really nice about this structure is just referring to the notes in yellow.
978.57 -> It's a linear structure that we saw before, right? If I start at the root, it just goes
981.72 -> 5432. And one. And so I know that in general, based on my initial input of n, like the length
987.85 -> of this path that has a number of nodes highlighted in yellow, there's going to be about n different
992.579 -> nodes. If I kind of adapt that language for like the tree, I can also say that the height
997.43 -> of this tree is n. So the height of a tree is really just the distance from the root
1002.31 -> node all the way to the far this leaf, in this case, that just means the distance from
1006 -> our top level called Five, all the way down to a base case, which is going to be exactly
1010.91 -> five here. Something you may also hear in passing is we can say that the number of levels
1015.48 -> in this tree is also n, this term is pretty straightforward, right? When we say the number
1020.26 -> of levels, a level is just a collection of nodes that are the same distance away from
1025.159 -> the root. So for example, here in yellow, I have highlighted level zero, this is level
1029.329 -> one, this is level two, this is level three, and so on. But if I rewind things a bit, I
1034.399 -> look at the very, very top level, there's one node here, on the next level, there's
1038.77 -> two nodes. On the next level, there's four nodes, then eight nodes, then 16 nodes, see
1044.529 -> the pattern?
1047.499 -> So let's try to generalize that. So I know no matter what, whenever we call some top
1051.57 -> level argument for dib, we know that we're going to have one node at the top level. But
1056.461 -> to get the number of nodes on the next level, we'll just multiply that by two. And the level
1060.139 -> after that would also multiply by two and multiply to again further levels after that.
1064.169 -> And I do this a total of n different times, right? Because I know that the height of the
1069.6 -> tree, or the number of levels in this tree is exactly n. And so what conclusion Can we
1074.09 -> make over here, we're basically saying that to get the total number of nodes, or the total
1078.33 -> number of calls a recursive function would make, you would just take the number two,
1083.109 -> and multiply it by itself about n times over. And that is really the definition of an exponent,
1088.35 -> right? This is the same as two to the nth power. So we can say that this tree structure,
1093.289 -> this recursive function has a two to the N, time complexity. Awesome. So we identified
1098.82 -> this dip function is having a two to the n time complexity. But what do we know about
1102.22 -> the space complexity over here, I think a common mistake I've seen people make is kind
1106.09 -> of automatically assumed that the space complexity of a recursive function will be the same as
1110.86 -> the time complexity. And that might be a reasonable trap to fall into, because we know that in
1115.33 -> the long run, we're gonna have to evaluate two to the n function calls. And so I guess
1119.249 -> that means you have to put two to the n function calls on the stack. But there's actually some
1122.88 -> nuance to this, the time complexity of this is not the same as space complexity. So let's
1127.609 -> jump in. Let's say that we made our top level call to dibba five, we know that that is added
1131.539 -> to the stack in the same way five calls for so it's that the stack, we add a stack frame
1135.759 -> for every call that we make down until just the base case, right. So at this point, I
1140.289 -> reached a scenario where I have a base case, and I'd have about five stack frames on my
1144.239 -> call stack. And the important insight is when we actually hit this base case that I have
1148.85 -> highlighted the left one over here, it actually will return when something returns when a
1152.799 -> function returns, its stack frame is actually removed or popped from the stack. And at this
1158.07 -> point, only after I have returned from that left one, what I actually add the right one
1162.879 -> to the stack to be explored. And this process continues. Notice that any point in time the
1167.979 -> most number of stack frames that we use up, it's exactly five, right, it's not as if we
1172.72 -> throw all of these function calls on the stack at once. And because we have such a nice tree
1177.69 -> visual, we know that the number of stack frames that we're ever going to use is really just
1182.279 -> the height of the tree, right, so the height of the tree is n like we said before, that
1185.899 -> means our maximum stack depth is also n. So we have in space complexity coming from the
1191.139 -> call stack. So overall, for a dip function, we're looking at two to the n time complexity,
1195.61 -> but only and space complexity. Alright, so let's look at one more function. See, I gave
1200.729 -> you this very similar lib function. Notice that in its recursive calls, it does an n
1204.919 -> minus two. So by now you should be able to visualize what structure like this would look
1209.379 -> like, say we initially called lib with a value of eight, what would the full tree look like?
1214.009 -> Well, it would just look like this. Notice that it's still a tree, right where every
1218.94 -> node branches to two children. But this time, we go by twos, right, so if I look for some
1223.22 -> familiar ground here, I've noticed that from one node to the next, I do one minus two.
1227.59 -> And this occurs all the way down to a base case. So we already know that, hey, we can
1231.73 -> identify this tree as having a height of about N over two. So I guess that means that the
1237.259 -> time complexity is going to be two to the N over two power, right, because from one
1241.119 -> level of the tree to the next, we double the number of nodes. So that's that two times
1245.119 -> two times two repeating pattern for the number of levels and our number of levels is N over
1249.22 -> two, right? However, we can actually simplify this time complexity, you can take that n
1254.08 -> over to in the exponent and simplify that to just an N. So overall, we're looking at
1257.94 -> a two to the n time complexity. And using our same arguments from last time, we know
1261.899 -> that the space complexity for this from the stack is also going to be N over two, which
1265.58 -> simplifies nicely to N space complexity. So we see that overall for our loop function,
1270.869 -> we're looking at a two to the n time, but no space complexity.
1273.919 -> Alright, so now it's time to look at the big picture, we looked at two toy functions of
1278.909 -> dibben lib. And we saw that the only difference in how they made the recursive calls, right
1283.169 -> did did a minus one and lived in a minus two. And we saw that despite their differences,
1287.019 -> both functions had an exponential two to the n time complexity, and a linear and space
1291.83 -> complexity. That being said, Where does our original Fibonacci function fit in this picture?
1298.09 -> Well, you can imagine that kind of falls right in between, we know that for our fib function,
1303.799 -> it has two recursive calls for the first recursive call, it makes it does n minus one like did
1308.2 -> did. But its second recursive call this n minus two like lib. So in a sense, it's kind
1313.11 -> of like smack in the middle, right? If you let me abuse the notation a little bit, and
1316.599 -> just talking about the time complexity, we can kind of say that the complexity of fib
1320.21 -> is somewhere between dibben lib, but we've already plugged in some values, right? You
1324.7 -> know, that, hey, the lower bound that as Deb has to to the end, and the upper bound, that
1328.69 -> is lib has to at the end as well. So that means that our fib function must have exactly
1333.58 -> two to the n time complexity as well, right? All three of these functions have an exponential
1338.09 -> time complexity. Awesome. So that was a really complete analysis of why we have this fib
1342.919 -> function, it's evident that it has a two to the n time complexity and an N space complexity.
1348.239 -> Right now, the bottleneck that we're experiencing is, of course, the time complexity, right
1352.179 -> two to the n overall, is not undesirable complexity. But do we really have a nice feel for what
1358.359 -> this really implies? So let's take a look at this to to the end. So what's the implication
1363.029 -> of this? Well, you could kind of imagine that, hey, if I asked for the 50th Fibonacci number,
1367.48 -> that would take you know, roughly two to the fifth power number of steps. And so if you
1372.489 -> punch this exponent into a calculator, you're gonna end up with a result like this, this
1376.809 -> is roughly a 16 digit number. So you should have a vi for you know, this being a very,
1381.239 -> very, very large quantity. But I think that, you know, to really understand the gravity
1386.539 -> of what we're really saying here, if we expand this number, that quantity is exactly this.
1392.349 -> That's over one quadrillion 125 trillion, which is really, really interesting, right?
1398.539 -> Because we just asked our Fibonacci function for just you know, something relatively modest,
1402.69 -> right, the 50th Fibonacci number, and it's gonna take quite literally a quadrillion steps
1407.789 -> to do that. And of course, we can probably do better. So let's work on making this faster.
1412.24 -> So if I recognize that the bottleneck for this fib function is the time complexity,
1415.69 -> I know that that comes from the number of recursive calls that I make. So what I want
1419.409 -> to do is look for any patterns that I see in the recursive nature of this problem, let's
1423.679 -> see a quick snapshot of what the recursion for fib of seven would look like, we know
1427.749 -> that it looks like this tree just like we saw before. So take a moment, look at this
1432.159 -> tree, do you notice any interesting patterns within it?
1434.669 -> Well, one thing that I noticed is I can see this subtree rooted at three, there, I have
1440.879 -> two on the left and one on the right. And if I look at that subtree, I can actually
1444.09 -> see it in many different places in this tree, right? This subtree rooted at three appears
1449.09 -> in a bunch of different places, it's very duplicate. In a similar way, I can look at
1453.679 -> other sub trees, let's say I root myself at four, and see that duplicate four sub tree
1458.349 -> all over the place. And it's even carries over for larger values of n like fib of five.
1464.609 -> So I see that this tree has a lot of duplicate sub trees, right? And I want to now draw a
1468.149 -> connection between this diagram and what happens in my code. I know that if I route myself
1472.679 -> in any of these sub trees of five, I know that each sub tree is trying to answer the
1476.469 -> question. Hey, what's the fifth Fibonacci number? I know that the Fibonacci number never
1481.789 -> changes, right? If I calculate it once on the left hand side, then the answer I should
1485.679 -> get back on the right hand side shouldn't differ at all. And so what I want to do is
1489.859 -> possibly reuse these calculations right? If I calculate the Fibonacci number over here,
1495.119 -> then I should just store that because later on it might be useful when need to recalculate
1499.009 -> it over here. I would basically get rid of a lot of the tree, I wouldn't have to travel
1503.519 -> down this entire recursive tree rooted at five. This pattern of overlapping subproblems
1508.779 -> is known as dynamic programming. And so for us dynamic program is going to be any instance
1514.029 -> where we have some larger problem, in this case, Fibonacci. And we can decompose it into
1518.759 -> smaller instances of the same problem, we also have an overlapping structure. So for
1523.46 -> us, right, now, I see that I have to calculate now let's say fib of five, twice over to calculate
1528.299 -> the larger a fib of seven a solution. And something we're going to be doing a lot in
1532.419 -> this lesson is really trying to visualize problems in terms of like their recursive
1536.61 -> nature. So we're going to be drawing a lot of trees. And what I'm always going to do
1540.129 -> is try to really recognize, hey, what pattern in this tree is duplicate, right? If I do
1545.129 -> some duplicate work, if I do some duplicate drawing that I know I can optimize that out
1548.989 -> later on. But that being said, let's go ahead and get to the punch line on this fib function
1553.419 -> and work on optimizing this solution. Alright, now let me discuss the plan, I think we're
1559.21 -> ready to actually implement some code that will actually carry that plan out. Here I
1563.011 -> have our classic bonacci implementation, this was the one that we ran earlier. And it's
1566.799 -> definitely too slow, right has an exponential time complexity. So I know that overall, when
1571.36 -> I want to do is kind of capture a duplicate subproblems, I want to store any results that
1576.081 -> I get that way, if I have to recalculate those subproblems. Later on, I can just use my stored
1580.789 -> data. And so the trick here, it's a very common programming pattern, we're going to implement
1584.72 -> some memorization. memorization is actually one of the overarching strategies we can use
1589.88 -> to solve any dynamic programming problems. And so just look at the name like why is it
1594.33 -> called memoization? Well, this refers to like, memo, right? So if I have like a memo, and
1598.509 -> let's say, like real life, it's really just like a reminder for myself. So using memoization,
1602.549 -> I'm looking to do is store some duplicate subproblems. That way, I can just get those
1607.349 -> results later on. I think a really neat way to implement memorization in JavaScript, as
1612.02 -> well as many languages is to use some sort of a fast access data structure usually be
1616.769 -> like your hash map equivalent in the programming language of your choice. For us, that'll be
1621.789 -> a JavaScript object. And so our plan is to use some JavaScript object. And so what do
1626.211 -> I want the keys to be, so the keys in the object are going to be the argument to our
1632.86 -> function, right. And then the value will be the return value, that we have a nice napping
1640.09 -> for a argument to the function that is a function call, as well as its return value, right.
1646.21 -> And so what I can do for my existing function is, I kind of just bake in some optional arguments.
1651.109 -> So my favorite strategy is to do this, and assign a memo to be an empty object. So if
1656.58 -> you're unfamiliar with this syntax, in JavaScript, it's pretty useful. What I'm saying is, if
1660.599 -> I were to call our fib function, and not pass in a second argument, by default, it will
1666.309 -> create this memo, as containing a new JavaScript object that is empty, of course, right? So
1672.64 -> it's gonna be useful that way, whoever is actually testing, my code doesn't have to
1675.499 -> deal with setting up any memo object. So well, I prefer this strategy over here. And what
1680.33 -> I want to do is treat it as if that, hey, this memo is going to store n as the key and
1686.399 -> values are going to be just the return values for this function. So what we're going to
1690.389 -> see us doing a lot in this lesson is at the start, we're going to first check for existence
1695.919 -> inside of our memo. So let's say that we're somewhere in the middle of recursion, the
1700.44 -> first thing I should do is kind of add an additional base case and say, Hey, is my current
1704.499 -> argument n inside of the memo, and if it is, then I can just get the stored value from
1710.499 -> that memo. And I'm done. So I'm going to do an early return here, I'm going to return
1714.32 -> the value that corresponds to that memos key, right, I'm just using the original argument
1719.82 -> and as a key in my memo, and this condition is really just some classic JavaScript syntax,
1724.339 -> I'm just checking if some key is inside of a JavaScript object. So
1728.71 -> really quick, maybe just to warm us up, if you haven't seen that syntax before. So let's
1732.919 -> say I had some object and had some properties inside and we had a name of Alvin, that's
1739.519 -> me, then it had a favorite color of like gray. And what I can do is check for an existence
1746.809 -> of a key in that object. So JavaScript keys are mostly strings, right? So I can check
1751.03 -> Hey, his name in the object. That's true, is the fav color in the object, make sure
1758.969 -> I spell it right. That is also true, I can check for a key that's not there, like I don't
1763.84 -> know, his location and the object that is false. And so here, I'm just using that same
1768.809 -> pattern, but for and right, which is going to be a number, technically, it'd be converted
1772.71 -> into a string key, which would still totally work here. Awesome. So I have my memo fetching
1778.63 -> logic, right? I check, Hey, is this argument in the memo, but if it's not, I'm gonna have
1782.289 -> to actually manually do the calculation, which is okay, because I know I need to do a subtree
1786.469 -> at least once. So what I'll do is I'm going to take the exact return value, right, so
1790.629 -> this is my return value in the original brute force. What I want to do is actually store
1795.799 -> that entire result inside of my memo and the key is course and write, the key you use to
1802.279 -> access is always just what your argument is. And I want to complete the original return.
1806.72 -> So I can just go ahead and return what I just put in that memo. So I'm not really changing
1813.019 -> any return values, or I'm returning exactly the expression that I returned before. But
1817.809 -> now I'm actually also saving that value inside of the memo object. What I want to do is make
1824.489 -> sure that all of these recursive function calls are accessing the same memo. So what
1828.98 -> I'll do is I'll pass in that object to both of these calls are really important pattern
1833.649 -> here is, I know that I only receive a new top level metal object, whenever I make a
1839.529 -> top level call, that's a fit, right, because I'm not passing in a second argument over
1842.729 -> here. However, if I look at my recursive calls, I do pass in explicit second arguments. And
1849.009 -> so they're actually going to receive the same memo object, and it would be like passed by
1853.38 -> reference, right, because when you pass a JavaScript object to a function, you actually
1856.97 -> receive kind of that exact object, right, you don't receive a copy of it, which is really
1861.269 -> neat. So basically, I'm giving my function calls a way to sort of communicate to each
1866.119 -> other, they all have some sort of global information to reference across all the recursive trees.
1871.999 -> So this is looking good. Again, I just want to emphasize I only added a new argument over
1876.269 -> here, I added a new base case on line five, then I added my memo storing logic on line
1881.96 -> seven, but I didn't change any of the functional logic here. Let's go ahead and run these test
1886.97 -> cases. And we'll see how our code is doing now. So moment of truth, I'm going to run
1892.2 -> this file.
1894.46 -> And notice how blazing fast our program was, I still get the results of 813 and 21. And
1899.519 -> the 50th Fibonacci number is indeed have this very large number. And it basically executed
1904.559 -> almost instantly. So let's go ahead and head back to the drawing board. And really understand
1909.379 -> you know, what happens when we execute this sort of code. Alright, so it looks like we
1914.769 -> implemented the improved version of Fibonacci by memorizing it. And it's clear by running
1918.7 -> the program that we definitely had an impact on the time complexity. However, I really
1923.309 -> don't want us to understand you know how the structure of the recursion tree changes, once
1927.459 -> we implemented this memoized version of the code. So let's say I want to step through
1930.96 -> an example of Fibonacci. And let's say I pass in a number six. So really, what I'm looking
1935.379 -> for here is to get back eight, because the six number of the bonacci sequence is indeed
1939.46 -> eight. So we know that we're going to have a tree that looks like this, right? This is
1943.34 -> a tree that would really be the full recursive nature. So this is how the tree would look
1947.35 -> like if I did not optimize it. And what impact do we have by actually adding this memo object?
1952.899 -> Alright, so let's start tracing through this, I know that when I call fib of six at the
1956.379 -> top level, an important thing to know is I'm not going to pass in some memo object. So
1961.029 -> by default, my code says it will initialize it to an empty object. And the really important
1965.989 -> aspect of this is I'm going to create a new object just for the top level call. But then
1970.249 -> that same object is going to be passed down to my recursive calls, right noticing line
1974.859 -> five. And so I'm going to travel down my left hand path, right, six calls five, five calls
1979.919 -> for four calls three, and three calls to, and right now I've hit a base case. So I know
1984.97 -> that this note of two is going to return one. So that's kind of business as usual, right?
1989.259 -> In the same way, now I need to evaluate this other note of one, which is also base case,
1993.32 -> it also returns one. And at this point, my parent of three is actually ready to compute
1997.779 -> the sum of its children, right. So one plus one just gives me two. So I just add up those
2002.659 -> values. And now three is returning two. However, looking at line five of my code, not only
2009.009 -> is this node going to return to to its parent, it's also going to store it in the memo object,
2014.799 -> right? So the key insight is, at this point, I would add a key to my memo of three, whose
2021.539 -> value is to basically in my memo, I kind of read as if I'm saying, hey, the third Fibonacci
2026.159 -> number is two, right? That in itself is logical. So I can just continue this pattern right?
2031.859 -> Now I start to evaluate what happens when I'm at this note of t, which is also a base
2035.539 -> case, it returns one. Now my call to four is ready to compute, right? It's going to
2039.499 -> take the sum of its both children. So it's going to do two plus one gives me three. But
2044.589 -> of course, it's also going to store that inside of the memo, right? It's going to cash that
2048.389 -> result out to be used later on. So that means I make the N my key and I make the return
2054.23 -> value, the value, right, so I'm going to have four points to three inside of my mental object.
2060.139 -> And now here's the beauty of this memoized solution. At this point, I need to compute
2065.619 -> what happens for fib of three. However, this is actually going to hit one of the new base
2070.359 -> cases I added, right? I know that three is in the memo object looking at line two of
2075.48 -> my code, right? It's in the memo object. And so I just immediately return the stored value.
2081.28 -> And so this call to fib of three is just going to return the stored value of two. And if
2086.83 -> I do that, I won't have to travel down the full, you know, recursive subtree rooted at
2091.48 -> three, right? I don't need to travel to these nodes at all. So I already used some stored
2096.179 -> value in my memo. At this point five is ready to return right five will take The summer
2100.4 -> both with children, three plus two is five. And it's going to store that in the memo,
2104.25 -> right? The fifth Fibonacci number happens to be five. And now the same thing happens
2108.36 -> for six is right child. So I have to evaluate this node now. So what is the fourth of bonacci
2113.859 -> number up, that's actually stored directly inside of my memo. So this actually early
2119.34 -> returns the stored value of three, right. And again, the key insight is, it will return
2124.9 -> that stored memo value without having to travel through that full subtree. And at this point,
2129.91 -> I can return for fib of six, so I do five plus three, that gives me eight, this would
2134.09 -> actually technically be stored in my memo as well. So I have the six of bonacci number
2137.809 -> as eight and very happy, right? The answer is eight.
2142.71 -> So it's evident that by memorizing our Fibonacci function, we definitely cut down on the number
2147.47 -> of recursive calls, we want to visualize that we ended up with this kind of structure. Here
2152.251 -> in light blue, I have a circle the nodes that we technically didn't need to do the full
2157.319 -> recursion at, right? So these nodes of two, three and four, for this small example of
2161.23 -> my initial fib of six, I was able to kind of fetch a value from my memo and kind of
2167.53 -> forego traveling down the recursive subtree. So what do we actually know about the time
2172.819 -> complexity of this function now? Well, I think it's really important that we always try to
2177.029 -> generalize things. So we just stepped through a relatively small example of fib of six.
2181.079 -> But how does this sort of scale? So this is the same tree? Let's see, I kind of tidy it
2185.49 -> up a little bit. So I know that in general, fib of six kind of has this structure. So
2190.69 -> I want you to take a moment, look at this. And in your brain picture, what fib of seven
2195.619 -> would look like if I memorize with with seven, what would its sub trees look like? Well,
2200.47 -> it really just looked like this. Notice if I route myself at the seven node, its left
2205.74 -> child is still six, right minus one. Its right, Charles, still five minus two. So this still
2210.549 -> obeys the laws of Fibonacci. If I asked for fib of eight memoized, the structure would
2215.191 -> be like this. And nine would look like this. See the pattern looks like at this point,
2220.24 -> we're just growing this like memoized chain in a linear kind of way. If you're not convinced
2225.63 -> by you know, that just like structural argument, let's say we were a little more methodical,
2229.819 -> we know that in this drawing, there is some common ground, right? Again, like I always
2234.119 -> say, when you tackle these new problems, or new patterns, try to find some familiar territory,
2239.84 -> right? Where can I recognize stuff I've seen previously. So I look at this chain, I go
2243.88 -> 98765432, right. So this goes all the way down in a very, very, very linear fashion,
2252.059 -> right, just counting down. So I know that if I just look at this highlighted chain in
2256.049 -> yellow, that's definitely just an notes, right, where n is my initial top level call. So I
2261.2 -> know that what I have highlighted in yellow is just a linear chain right there exactly
2264.46 -> n nodes. But I haven't accounted for everything in this picture, right, some of the nodes
2268.39 -> are still in white, meaning I need to kind of work them into my description of what the
2272.079 -> shape of this tree is, well, I know that each of those white nodes is actually connected
2276.309 -> to some of the nodes, I've colored in yellow, they're kind of paired off in a way, right?
2280.24 -> For every node in my ello chain, it has exactly kind of one partner node one right hand node
2285.39 -> on its right hand edge. And overall, if I have n pairs, that means I have to end things
2291.19 -> overall, right? There are two things in a pair. So overall, the number of nodes is roughly
2295.43 -> two n. And I know that that can actually be simplified, right? The time complexity here
2299.24 -> would be two n, which just simplifies to n time complexity. And using the same arguments
2303.829 -> we did for our space complexity, we know that the space is going to be n as well. And this
2308.76 -> is pretty powerful stuff. by memorizing our fib function, we brought it down from an exponential
2313.94 -> time complexity to just a linear time complexity. Pretty cool stuff, right? All right, I think
2320.599 -> we're ready to graduate from Fibonacci. And what we'll do is work on a more involved problem,
2324.12 -> this one has more of a narrative to it. So say that you're a traveler on a two dimensional
2328.49 -> grid, you begin in the top left corner, and your goal is to travel to the bottom right
2331.76 -> corner of that grid. And the rule is you can only move down or to the right, that means
2335.809 -> you can't move up or to the left, and you definitely can't move to AGL. That being said,
2340.45 -> In how many ways can you travel to the goal? That is how many ways can you travel to the
2343.85 -> bottom rate if you had a grid of dimensions, m by n. So the first thing I recognize here
2348.76 -> is looks like the grid, maybe a rectangle, not necessarily a square, right. And overall,
2353.69 -> what we want to do is implement a function that calculates this that is our function
2357.64 -> is to take the dimensions of the grid. And so before we hop into sketching the strategy
2362.19 -> for this one, I think it's really important that we make sure that we actually understand
2365.46 -> what this question is asking. So let's say they asked us to calculate grid traveller
2369.851 -> of two comma three, that means they're asking us in how many ways can you travel from the
2374.059 -> top left to the bottom right, of a two by three grid? I will tell you right now that
2378.16 -> the answer here should be three. Right? So there are three ways to do that. And in particular,
2382.49 -> you can imagine that we had, you know, a two by three grid, so that means two rows, as
2386.589 -> well as three columns. And we start in the top left, and our end goal is in the bottom
2391.15 -> right. And so why do we say that there are three ways to get from the top left to the
2395.309 -> bottom right? Well, they told us in the problem that we can only either take a right move
2399.43 -> or a down To move, so one of the ways would be going right right down, right, that would
2403.559 -> bring us from the start to the end. And it would kind of look like this. In a similar
2407.509 -> way, another path we can take is doing a write down, right? It would kind of look like this.
2413.42 -> And the only other way would be going down, right, right, which would look like this path.
2418 -> And that's why we say there are only three unique ways to travel not through a two by
2421.32 -> three grid.
2422.4 -> All right, so that's just one example of how we might call a grid traveler, I think what's
2427.349 -> really important to notice is try to also frame the problem as if you were given some
2431.769 -> relatively small inputs, but I think they're really important case to think about is, what
2435.821 -> happens if they give us like, basically, the smallest valid grid we can have that is a
2439.769 -> one by one grid. This one is kind of trivially simple, right? A one by one grid only has
2445.18 -> one unique way to travel from the start to the end. And it's kind of concise in that
2450.069 -> if you had a one by one grid, there's really only one position, right? So that means that
2454.599 -> the start is really the same as the end, and kind of you already have the problem solved
2459.47 -> out the gate, because to travel from the start to the end, you kind of just do nothing, right?
2464.509 -> So the logic here is in a one by one grid, there's only one way to travel from the start
2469.71 -> to the end, you're kind of already there. So you don't need to take any moves. Something
2473.471 -> else we might think about is what happens when one of our dimensions is zero. So let's
2477.96 -> say someone asked us to calculate grid traveller of zero comma one, this is a kind of strange
2482.529 -> question to ask because for one, if there are zero rows and one column, that kind of
2487.21 -> means that the grid is empty. So I would consider that as there being zero waste to travel through
2492.39 -> that grid because the grid is sort of invalid in a way. In a similar way, if I asked you
2496.59 -> for traveling through a one by zero grid, there's still no grid to be found here. Right?
2501.819 -> So it should also return zero. And likewise, when either of the dimensions is zero, the
2507.619 -> answer should just be zero, right? If one of your dimensions is empty, then there really
2511.93 -> is no grid.
2514.5 -> So maybe you're catching on into why I brought up those trivially small grid examples, right?
2518.47 -> Those kind of sound like base cases, which we can use to reconstruct a larger answer.
2522.809 -> But let's stay grounded and look at another grid. So let's say I asked you to calculate
2527.059 -> grid traveler of three comma three, right? So I want the answer for a three by three
2530.599 -> grid. Well, how can we reason that one out? At this point, what I'm looking to do is sort
2535.92 -> of frame the problem in a way where I can decrease the problem size, usually by mutating
2541.069 -> the arguments to my function call. So let's imagine this three by three grid, as always,
2546.059 -> I want to move from the top left to the bottom, right. So I begin at this position, let's
2550.569 -> say, and so I know that overall, I have this top level problem of saying, how many ways
2555.2 -> can I travel through this entire three by three grid, let's say I took one of the moves,
2560.47 -> right, I can move either right or downward. Let's say I made the decision to move downward.
2564.38 -> Well, if I move downward, then I would appear here now, I know in the future, I can only
2568.64 -> move to the right or downward. So now it's as if my playable area has been reduced to
2573.02 -> just this shaded region. And if I look at what I've actually done to this problem, I'm
2578.77 -> still sort of in the top left corner of some grid, where now I'm really trying to answer
2584.319 -> the question, hey, and how many ways can I travel through this two by three grid. And
2590.65 -> this is a really important way that we're shrinking the problem size, right, I had a
2594.329 -> three by three grid initially. Now I have a two by three grid. And this is really like
2599.16 -> the relative grid from my current position, we'll say, spanning all the way to the end
2604.69 -> position. So if you look at this coordinate of like two comma three, technically, that
2608.819 -> is like not the coordinate of the person within the larger grid. This is really the size of
2613.99 -> the rectangle that the person is trying to cover now. Right. So in a similar way, let's
2619.02 -> say that, hey, I want it to move to the right now. Well, that would also shrink the grid
2622.849 -> along a different dimension. So I'd appear over here. And now I have a two by two grid
2627.29 -> that I'm trying to solve. And if I keep following this pattern, we're gonna keep shrinking on
2631.22 -> the problem size over here. Now, I'm asking for a grid traveller of one by two. And finally,
2636.329 -> if I take a right move over here, I'm asking for grid traveller of one by one, which I
2640.94 -> know is one of those sort of base case scenarios that I had previously. So that's going to
2644.99 -> be really useful when we actually implement this in some code. And so the key insight
2648.98 -> here is when we make a move through the grid, that is when we go right or downward, are
2653.059 -> basically shrinking the effective size of the playable area, right, our grid gets smaller
2657.88 -> along one of its dimensions. Awesome. So I think now we're able to see that, hey, this
2662.46 -> grid traveler problem definitely has some overlapping subproblems. Right. Let's say
2667.3 -> I tried to take more of like a programmatic approach, something that we'll be doing a
2670.4 -> ton, like, basically, for every problem in this lesson is whenever we have a problem
2675.21 -> that we kind of know is going to be recursive. The move is to really structure it like a
2680.589 -> tree, right? I want to really visualize this because I know that if I had a tree structure
2684.01 -> on pen and paper, I can implement that with just some recursive code using some JavaScript,
2688.789 -> right. So let's say that I wanted to take more of a tree based visual understanding
2692.839 -> now a grid traveller of two comma three. So I know that usually the way we do this is
2698.39 -> we encode nodes of the tree Using the arguments to this function right? Now I have two arguments
2703.44 -> where I have the number of rows and number of columns. Whereas before for something simple
2706.84 -> like Fibonacci, I only had a single argument. So in the long run, I know that the answer
2711.38 -> I should get back is three. So just keep that goal in mind. Let's start to flesh out this
2715.13 -> diagram. So I'm going to start with that top level, column two comma three. And now I think
2719.14 -> about how this node can transition to other nodes, right? How does the State of my game
2724.41 -> actually change? Well, I know there are really two options I can take right? You can either
2729.579 -> have one child going downward, or another child going the right word, right? Those are
2733.31 -> the two options based on the gameplay here. And so if I go down, how does that change
2739.359 -> of the number I put in the node? That is, how does it change the dimensions of the playable
2744.44 -> grid area? Well, if I go downward, then I'm reducing the number of rows by one. So that
2749.66 -> means my left child is just one comma three. Now, if I had moved to the right, that would
2754.23 -> mean I'm reducing the number of columns by one. So my right child is just two comma two.
2759.569 -> Notice that from parent to child, all I'm doing is reducing a one of the dimensions
2763.89 -> by one, right, that means you're either going downward or going to the right, and I can
2767.72 -> just carry over this pattern recursively, right. So let's say I'm fleshing out this
2771.799 -> node now, five, mat one comma three, then its children would be just 03. And one, two.
2776.859 -> And a similar way. If I wrote myself at two, two, then I have children of one, two, and
2782.15 -> two, one. But now let's notice something important. Here. I've kind of spotted a node where I
2787.45 -> have zero comma three, remember what we trace through in terms of the visual drawing? What
2792.91 -> is this? No trying to answer? Well, let's notice saying in how many ways can you travel
2797.4 -> through a zero by three grid, but if you have zero rows, and there's really no grid to be
2802.41 -> dealt with. And so I think for this note of zero comma three, or really any node that
2807.019 -> contains zero, we don't need to actually flesh out its children. So instead, I'll work on
2812.19 -> recursively, drawing out these other nodes. So we'll carry over this pattern. At this
2817.81 -> point, we've actually hit I think all of our base scenarios, right. So if I look at these
2822.59 -> notes, that have one comma one that was exactly like the affirmative base case, meaning that
2828.609 -> I know, trivially I can solve the one by one grid. In other words, these function calls
2834.39 -> are going to return one, right, in a one by one grid, there's exactly one way to travel
2840.079 -> from start to end, right, you kind of just do nothing. And on the flip side of that,
2843.66 -> for all those notes that contain a zero, they're also a base case. But they're kind of like
2847.64 -> the negative base case, meaning that there is no way to actually travel from start to
2852.29 -> end. So for all of these notes containing zero, they should return zero to their parent,
2856.68 -> right? There are zero ways to travel through that grid. At this point, I just sum up these
2860.46 -> values at the parent node, right? So I just left these and then I add them together. And
2865.19 -> I forget what these things are saying, right? If I look at like the node one comma two,
2869.56 -> above, it is one meaning there is one way to travel through a one by two grid, right?
2874.24 -> You kind of just move rightward. And so I carry this pattern over, I keep adding the
2879.349 -> children nodes at their parents. And this carries up all the way to the root node. And
2884.92 -> what do I know, there are three ways to travel through a two by three grid. So although there's
2889.22 -> like a narrative, and there's some, you know, cute story behind this problem, it's really
2893.359 -> just a spin off of Fibonacci. And that's going to be the case with many of these dynamic
2897.94 -> programming problems. So we've confirmed that there are three ways to travel through a two
2902.329 -> by three grid. And there's also some other information encoded in this tree. I know that
2907.039 -> whenever I took like a left hand edge in this tree that I presented the choice of going
2911.65 -> downward. And whenever I took a right hand edge in this tree, that represented the decision
2916.91 -> of moving rightward. And so if I have that pattern in mind, that not only have I been
2923.15 -> able to count the number of ways that I can win the game, but I also know exactly which
2928.319 -> combination of moves lead to a solution. One of the ways to win this game is to go down,
2932.73 -> right, right. Another way is to go right down, right, and the final ways to go right right
2938.079 -> down. So we can glean a lot of information just from the same tree structure. Cool. So
2943.68 -> I think that's enough drawing for now let's go ahead and implement this in some code.
2947.5 -> Alright, programmers, here we are back in my text editor. Let's go ahead and implement
2952.02 -> this grid traveler function. So you want to go with that recursive strategy. So I'll start
2956.279 -> by just laying down my base cases, I already refer to the fact that hey, one of our base
2960.359 -> cases is we have a one by one grid, just go ahead and return to zero, right? So that's
2964.63 -> really easy to do. So I'll check Hey, if m is one, and n is one, then the answer is just
2971.97 -> trivially one. Right? Then along with that, I also have the base case when I had like
2975.68 -> an invalid grid. That means if either of my dimensions is zero, so I'm using an order
2981.9 -> here, right? If either dimension is zero, then your grid is empty, which means there's
2985.47 -> definitely no way to travel from top left to bottom right of that grid. Cool. Then my
2991.249 -> recursive scenario is very straightforward. All I need to do is get the sum between me
2996.599 -> going downward and me going rightward, right. So if I go Down number n is the number of
3002.2 -> rows, if I go down that I'm decreasing the number of rows affected rows, that is by one,
3006.029 -> but I keep the same number of columns. And then in a very symmetric way, if I move to
3011.19 -> the right, that means I still have the same number of rows. So I keep him, but I decrease
3016.069 -> n, right, I have one less column. So this code is looking pretty sharp, really just
3021.22 -> some reminiscent Fibonacci style code. Let's go ahead and give this a run. So I'm running
3025.599 -> a few examples over here. And these are the expected results. In some comments, we'll
3031.29 -> go ahead and bang this one out, grid traveller. Cool. So I get the first four results. 1336
3037.73 -> looks like it's working just like a charm. However, for this last example of an 18 by
3042.631 -> 18 grid, looks like my programs hang right now, it's actually a little too slow to calculate
3049 -> an 18 by 18 grid, so you probably see where this one's heading. Let's go ahead back to
3053.569 -> the drawing board and really understand a why this code is fairly slow. Alright, so
3059.329 -> here we are back in the drawing board, because it looks like we hit a wall when it came to
3062.749 -> our recursive implementation of grid traveller. That being said, I think implementing the
3066.869 -> brute force solution here is actually a really logical first step. And now we can just focus
3071.1 -> in on where there is room for improvement. So our first question is, you know, what is
3075.46 -> the actual time complexity of this implementation? Well, I know when I call a grid traveller
3079.661 -> of two, three, the full tree would explore is this right. And like we said, in our Fibonacci,
3085.44 -> to understand the time complexity here, it's really about to understand the number of function
3090.23 -> calls we make, which is really the number of nodes in this tree. And of course, I want
3093.71 -> to generalize my understanding for any large inputs. So I know that this tree sort of looks,
3099.15 -> you know binary in a way, meaning that a node can branch to up to two children, which makes
3103.299 -> sense, because from one position of the grid, I have two choices to make, right, I either
3107.98 -> go down or I go to the right. But that being said, I need to realize what the height of
3112.63 -> this tree is. And this one's pretty interesting, because out the gate, we actually have two
3117.319 -> input arguments to our function, I'm given two numbers, m and n. And since this function
3122.119 -> contains two inputs, it shouldn't be the case that our final complexity analysis actually
3126.619 -> describes it in terms of those two number inputs. So what I'll do is try to recognize
3131.859 -> the height of this tree. And so I'll just choose some path from the root to the leaf.
3136.67 -> And kind of in the recursive sense, that means I take some path from my top level call all
3141.089 -> the way down to the base case, and preferably the far this base case. So I know my base
3146.2 -> cases are either when one of my arguments turns to zero, or when both of my arguments
3151.339 -> turn to one. And I think in general, the farther base case would be a scenario where both of
3156.18 -> my arguments are one. So here I have a path highlighted that ends in a one one. And in
3161.45 -> general, I know from one node to the next, what I do is either decrease the M by one,
3168.249 -> or I decrease the n by one, it's never the case that I can decrease both of the numbers
3172.859 -> by one, because that would kind of entail that you're moving diagonally across the grid,
3176.349 -> which is not allowed in the gameplay. So if I can either subtract one from em, or subtract
3181.569 -> one from n, and overall a path from my initial input down to a space, like one one is going
3187.44 -> to have a distance of n plus m, we basically had to subtract, you know, n and m from your
3193.38 -> initial node to reach that bottom level of one comma one, that kind of tells me the number
3198.341 -> of levels in this tree, right using the same arguments as before, it's still the case that
3202.88 -> most of this tree is going to be binary, meaning that a node branches to two children. And
3208.15 -> so I have that two times two times two pattern for the number of levels in this tree are
3212.44 -> saying that they're n plus m levels. So really, the time complexity is two to the n plus m
3218.15 -> power, right. So still some sort of exponential, that was just sort of the multi variable exponential,
3223.41 -> because I have two variables of m and n.
3227.589 -> And likewise, for the space complexity, you know, the space complexity, in general for
3230.529 -> recursive code is just going to be the height of the tree, the height of the tree gives
3234.43 -> us the maximum stack depth of the recursion. In this case, it'll just be the number of
3238.58 -> levels still, which is n plus m. Cool. So obviously, the limiting factor here seems
3243.46 -> to be that time complexity of two to the n plus m time. Alright, so you probably anticipated
3249.259 -> the time complexity of this one being exponential. That being said, I think it's really important
3253.46 -> to still have a nice argument to you know, say what the time complexity is. That way,
3257.46 -> you can get a massive buy in from your interviewer right. So don't skip the steps of trying to
3261.39 -> draw it out and actually defend your reason for why the time complexity is exponential.
3266.559 -> But let's try to improve this now. Is there any room to actually improve the runtime of
3271.24 -> this function? Well, here's that same drawing, right? Just sort of cleaned up. So I have
3274.99 -> grid traveller of two comma three, and this would be the full tree. We sort of already
3278.7 -> know now that I have this recursive tree structure. What I could possibly do is notice any duplicate
3284.04 -> work, right? Is there some work I can prevent myself from doing here. So take a moment and
3288.17 -> really remind yourself of the actions we took in the Fibonacci problem. And see if there's
3292.24 -> any patterns in this tree, probably from the gate, you notice that? Oh, I have some duplicate
3297.869 -> sub trees right? Have one comma two. So in these the two highlighted sub trees in blue,
3303.74 -> that's sort of asking the question, hey, what is the total number of ways I can travel through
3309.259 -> a one by two grid? That being said, I think there's even more sub trees that correspond
3314.26 -> to that particular problem. What if you look at this far right subtree of two comma one,
3319.88 -> if you really, really think about it, asking the number of ways to travel through a one
3324.97 -> by two grid is the same as the number of ways to travel through a two by one grid, you're
3329.42 -> just flipping the rows and the columns, but the total number of ways should be exactly
3333.53 -> the same. So that's actually a pretty interesting way you can optimize this solution. So I can
3338.3 -> still memorize it. That is I have these duplicate sub problems. But possibly a really cool insight
3343.3 -> we can make in this problem is, you can also sort of flip of the arguments. In other words,
3348.239 -> if someone asked you, for grid traveler have a comma b, then that would be the same answer
3353.859 -> for a grid traveler of B, comma a, write the order of the arguments technically doesn't
3358.63 -> matter here, until we can totally encode that in our memo object. That being said, punchline
3364.15 -> here is, we better memorize this one. So I think let's hop to it. All right, here we
3369.369 -> are back in my text editor. And hopefully, that diagram of how we can improve the time
3373.559 -> complexity for this one, make some sense, let me go ahead and kill this program still
3377.85 -> running. And my computer's fan is actually going crazy. Now it's running the entire time.
3382.14 -> Let's go ahead and memorize this one to the beauty of you know, solving these dynamic
3386.779 -> programming like problems is if we have the initial strategy of implementing, like the
3390.829 -> brute force recursion, and we wrote like a very well formed recursion, meaning that I
3395.739 -> actually use return values, and I reconstruct the sub solutions all the way up to the tippy
3400.94 -> top. Because I have a really nice recursion here. memoization is a very like formulaic
3406.4 -> pattern. So it's going to be almost the same strategy, we did have Fibonacci, even other
3410.21 -> problems that we do in this lesson. So we're going to bake in my default mental as an empty
3414.16 -> object. And then along with that, I'm going to add my memo checking logic. So I know we're
3419.93 -> here in general, and to check, hey, are the arguments in the memo, right, I need to key
3427.42 -> into my memory object using all of the arguments. Now I have two arguments. And since both of
3432.569 -> the arguments combined, sort of dictate the output, I need a key that sort of contains
3437.48 -> both of those in JavaScript keys are either strings or symbols. So for us right now, really,
3442.26 -> strings is the most relevant one. So what I'll do is I'll concatenate both of these
3445.859 -> integers together. That way, I have a string which I can key into the object with. And
3451.65 -> we'll all definitely want to do is also maybe separate them with a comma, let's say, so
3456.63 -> I'll say like key equals m plus a comma, plus, and, and the reason that you probably want
3463.9 -> like some sort of separator between these two arguments in your key is to make sure
3469.71 -> the numbers don't get misinterpreted. So I've actually ran into this issue in the past,
3474.88 -> can you imagine like, if I had a scenario where, let's say m,
3478.259 -> was 42. And then we'll say n was a number three. So what I don't want to do is just
3488.28 -> make my key, something like four to three, because this isn't going to uniquely identify
3495.39 -> the exact input argument, right? Because if this was just my key, then I guess I have
3501.999 -> the same key for a totally separate set of arguments. If I gave you four and 23, right,
3508.72 -> both of these combinations of very different arguments would lead to the same key, they
3513.92 -> would collide at the same key. So instead, I'll prefer to put a comma between them right
3517.319 -> now I know without a doubt that this key corresponds to four comma 23. Right. So that's why I put
3523.16 -> a separator between them. And depending on the language that you know, you're choosing
3526.47 -> for interviews, you can find a very similar construct. Cool. So now I'll go ahead and
3530.55 -> check, hey, if my key is in demo, then I have the Result Cache already. So I could just
3539.859 -> return net cash value. So return memo at key. And then what I want to do is look for my
3545.22 -> old return value, right? So here's where I actually do the manual recursive calculation.
3549.819 -> Before I return that I want to store it in my memo, using the same key and then return
3556.39 -> that thing, I just put in the memo just completing the old return logic. Cool. So you've seen
3562.02 -> this pattern before, I just want to emphasize a few things. Now it's the second time we're
3565.02 -> seeing it. memoization, for me, at least, is a very, very formulaic thing, right? So
3570.65 -> I always take the exact expression that I returned previously, and I put that entire
3574.849 -> thing in the memo object, right? Notice that my key encodes the arguments for this function,
3581.73 -> right? Mnn. Something that I've seen students do in the past is do like very heavy handed
3586.76 -> logic, where they try to, you know, check if the child call is in the memo. In other
3593.31 -> words, don't write any pre emptive logic where you check Hey, if you know m minus one And
3602.02 -> so if you like concatenate those two things together, don't check if like your child's
3605.91 -> key is in the memo, right? So imagine like, this was my key right now. Right? don't check
3612.239 -> if that is in a memo. When you write logic like that, it ends up being very, very duplicate
3617.89 -> and a little harder to debug, right? Instead of writing your function as if it's, you know,
3623.14 -> reasoning for its children calls, you know that once you actually evaluate those child
3627.48 -> calls, they're going to cache themselves, right, they're eventually going to check this
3631.68 -> if statement anyway, alright, so don't do any like the look before you leap logic, just
3636.069 -> make the recursive call. That way, every recursive call doesn't like self service, right? So
3641.089 -> I prefer this way implemented. And with that, let's go ahead and run this code. So I'll
3646.47 -> give this a shot grid tremor, I still have that 18 by 18 grid, which took quite long
3651.039 -> last time didn't actually finish while I was waiting. Looks like I'm still hanging here,
3656.019 -> because I'm actually missing a little bit in my code. So take a moment, see if you can
3659.67 -> spot it, I forgot one really important thing. So I have the logic of checking if my key
3664.21 -> exists in the memo. And I also have a logic of storing something in my memo if the key
3667.73 -> doesn't exist, but what I failed to do was actually passed down the memo object to all
3672.9 -> of the recursive calls, I want to pass it down over here. Remember, the trick is only
3678.23 -> top level when someone calls like two comma three for grid traveller, then we're going
3682.46 -> to initialize a brand new empty object, which will be shared for the rest of the recursion,
3687.26 -> because it's passed down by reference at this point. So that's a common mistake.
3691.56 -> Let's go ahead and run this now. Nice. And there we have it. Look how quick our last
3696.489 -> execution was over here now is the expected answer for an 18 by 18. grid. Cool. So here's
3702.369 -> what we'll do, it's evident that we definitely set up the execution of this one, let's head
3706.799 -> back to the drawing board to wrap up this problem and really see how we cut down the
3711.319 -> complexity here. All right, so it looks like we definitely improved the runtime of our
3715.839 -> function. But I want to really understand you know, what the big O complexity of our
3719.74 -> improved function is now. So sort of a way we constructively argue for what the new time
3725.499 -> complexity is, is to think about where the values of the nodes that will actually have
3730.029 -> to travel through. So let's say I looked at this example, I wanted to find the number
3734.059 -> of voice a traveled through a four by three grid, I know in general, there would be a
3737.97 -> top level node, of course, four comma three. But in general, that refers to m n, right?
3742.93 -> So I'm trying to think in a very general way right now. And so I know when it comes to
3748.89 -> the other nodes of this tree, they're all going to sort of at most be four comma three,
3755.599 -> but then probably be less than that, right? They have like a range of values for the nodes.
3760.48 -> It's not as if to solve grid traveler, four, three, I would have to solve 530, right, that
3765.989 -> would be a larger grid. That doesn't make sense here, right? I'm shrinking the subproblems,
3769.579 -> only in this rendition of grid traveler. And so if I think about some possible values here,
3774.549 -> I know that if M is for top level, then all of the values for m in the rest of the tree
3780.41 -> would be from zero all the way up to and including four, right, it's never going to be bigger
3784.999 -> than four. In a similar way, since n is three here, the only possible values for n in the
3789.839 -> rest of my tree are zero through three. Right? So roughly Mele, we're a little off by one
3795.05 -> here, because I have to include zero, because we know that that's a base case, in our edition
3798.119 -> of this problem. That being said, there are roughly n choices for the first number, node,
3802.93 -> and n choices for the second number in the node. And along with that, we know that we're
3807.619 -> not gonna have to travel through many duplicate sub trees, because we memorize them away.
3812.09 -> And so I think what I can say here is the total number of nodes you can possibly have
3816.359 -> is m times n, and I'll be the number of like distinct nodes, right? Because I have m choices
3821.92 -> for the first number in the node and n choices for the second number. I know that I'm going
3826.28 -> to really minimize any duplicate exploration through the memo object. So the really the
3831.92 -> implication here is we started out with our brute force recursive implementation, which
3836.13 -> looked like it was exponential in the time, right, it was two to the n plus m time. And
3840.519 -> then by memorizing this function, we were able to bring it down to n m times n, time
3844.77 -> complexity, which is much faster. Notice that the space complexity stays here, which is
3849.451 -> really fine, because n plus m is some sort of a multi linear function. Cool. So there,
3853.99 -> we have our nice optimal solution for this grid traveler problem. So key thing I want
3859.07 -> you to take away from this one is, although the initial narrative and the problem made
3863.769 -> it seem, you know, pretty specific, and pretty different from a previous dynamic programming
3868.5 -> problem, like Fibonacci, was really the same sort of story, the most important thing that
3872.829 -> we're going to sort of leverage throughout in this lesson that we leverage twice already
3876.16 -> is try to think about your recursive functions in terms of a tree, right, I get the most
3881.38 -> information out of the tree. And then from there, I can use that tree to not only implement
3885.65 -> a brute force, but to also recognize, hey, where can I optimize this brute force, that
3889.869 -> way you can reach for the optimal solution.
3891.869 -> Alright, so we've gone over two different problems together, and hopefully we're starting
3896.41 -> to notice how we tackled them in similar ways. I think it's about Tammy gave herself some
3901.039 -> guidelines for solving dynamic programming problems using a memoization strategy. So
3906.24 -> we'll call this our memorization recipe. And there are three different ways you can go
3910.17 -> about learning this topic of memorization. And you might have different recommendations.
3913.88 -> This is just my particular recommendation. So I think the most important thing to establish
3918.559 -> if you want to, you know, solve some dynamic programming problem using memoization, is
3923.479 -> to stick to to like high level steps, we definitely need to at first just have a solution that's
3929.109 -> recursive. So just make it work could be slow, that's okay. And after that, we make it efficient.
3933.759 -> I think this is where a lot of students sort of try to take on too much at once. They try
3937.941 -> to just solve it quickly all in one go. Right? To me, it should be a very separate process,
3944.42 -> right? First, I look for correctness in my solution. And then once I have correctness,
3948.599 -> then I look for efficiency in my solution. So when we go through step one, if I just
3952.779 -> want to get a working solution, then I have to start visualizing these problems as trees
3957.499 -> in dynamic programming problems, the gist of them are that we have some large problem
3963.099 -> that I can break down into smaller instances of the same problem. So when I visualize it
3967.989 -> as a tree, what I'm looking to do is figure out all right, in the nodes of the tree, that
3972.45 -> should represent a problem. And when I draw an edge between nodes, that should be shrinking
3977.609 -> the size of the problem. And depending on you know how your problem is stated, you'll
3982.559 -> have to figure out that logic, right? In the case of our Fibonacci, it was as simple as
3986.98 -> we know, we can decrement our values of n. But in the case of our grid traveler, what
3992.599 -> we had to do was travel rightward or downward. And once we do that, you want to implement
3997.779 -> that tree using recursion. What's great about a tree is it's already a recursive structure.
4002.72 -> Right? So how do you start to translate that kind of tree visualization to some recursive
4006.88 -> code? Well, you think about the leaves of that tree as your base case, right? Lately,
4012.759 -> for us, it's been about some small numbers, right? So like a grid of size one, or it could
4017.16 -> also be in the case of Fibonacci, just our initial seed values of n equals one, and n
4022.551 -> equals two. Now, once you have that baseline recursion, that's going to be your brute force
4027.67 -> solution. And so you'll want to test it. To me this testing step is really important,
4032.28 -> right? So if you pass inputs into your brute force recursion, it should give correct answers.
4038.099 -> Although possibly for large inputs, it may take a long time, right? To me, there's a
4042.5 -> big difference between code that is slow, and code that is wrong, right. So here, we
4047.47 -> should give back valid results, although maybe our code is a little slow. Once we have our
4052.38 -> working brute force solution, making it efficient using memorization is a very, very canned
4057.519 -> scenario. All we do is start by adding a memo object into the mix. So this memo object needs
4062.819 -> to have keys which represent arguments to our function. And the values of that object
4067.749 -> represent the return values for those function calls. We know that in our functions, a unique
4073.89 -> set of arguments should give me a particular result. So we're just having that sort of
4078.44 -> mapping inside of an object, you need to make sure that this object is shared among all
4083.44 -> of your recursive calls. One way you can do that is to pass them along as arguments. And
4088.329 -> lately, I've been doing that by giving myself a default empty object at the top of each
4093.19 -> of my recursive calls right through my top level call. And once we do that, we need to
4098.44 -> add a new base case into our code. So I'm not going to remove any of the old base cases,
4105.15 -> from my brute force solution to me, I'm just adding a new base case that captures the memo.
4110.549 -> In other words, if my arguments are in the memo object as a key, then I'll return the
4115.699 -> stored value, I refer to that as like my memo fetching logic, right? Looking up some stored
4121.029 -> value in my memo. And beyond that, the only thing we need to do is implement our memo
4126.23 -> storing logic. And it's as simple as going to exactly where we had our return values
4132.06 -> in our function. And then we just make sure that we add those return values into our memo
4137.15 -> object before returning, right, so I always look to the exact return expression, and just
4143.409 -> write some code around it, right storing that result into another mental object before I
4148.58 -> return it, right? Step two is actually very, very easy to implement, meaning it's very
4154.15 -> easy to memorize a brute force solution, it's really coming up with the brute force in the
4158.589 -> first place, that kind of feels more difficult. So as you're learning and practicing memorization
4163.869 -> for these dynamic programming problems, I highly, highly recommend you stay very methodical,
4169.089 -> and follow these steps, right? Don't try to efficiently implement an algorithm from the
4174.17 -> get go get a brute force working solution with recursion, and then implement it using
4180.31 -> memoization. Afterwards, right. And as you get more practice with this technique, soon,
4184.989 -> you'll be able to do everything all in one swoop, but I don't recommend that until you've
4189.23 -> definitely finished this course.
4191.279 -> So we'll be sure to follow these rules for falling problems. Alright, so I think it's
4198.28 -> tempting increase a difficulty and we're going to know Their dynamic programming problem.
4201.48 -> So let's work on this can some function, what I need to do here is take in a target some
4206.19 -> as an argument, as well as an array of numbers by function needs to return a Boolean. So
4210.38 -> true or false, indicating whether or not it is possible to generate the target some using
4214.501 -> some numbers from the array. And along with that, we have some constraints here, we can
4218.73 -> totally use an element of the array as many times as we want. And we can also assume that
4222.9 -> all input numbers, so the target sum as well as the numbers of the array are non negative.
4227.84 -> So let's try to understand what this question is asking. Let's say I gave you this example
4231.82 -> case. So it looks like our targets, I'm a seven, and the array of numbers is 534, and
4236.59 -> seven, here, the response should be true because it is possible to generate seven, by adding
4241.8 -> some amount of numbers from the array, one way you can generate seven is by just doing
4246.98 -> three plus four, another way would actually be to just take the loan seven, because seven
4251.53 -> is actually a member of the array. So it's definitely possible to generate seven using,
4256.239 -> you know, some amount of numbers from the array. So that's why we return true. Let's
4260.63 -> see, I gave you another example. Let's say I gave you a target sum of seven again, but
4264.07 -> I gave you a different array of just two and four, this is actually going to return false,
4268.57 -> because there is no possible way that combinations of two and four can actually sum to seven.
4274.04 -> Cool. So that's really what the question is asking. Now let's try to think about how we
4277.65 -> can frame this problem of recursively. Right? Hopefully, you've already gathered in your
4281.61 -> mind. If we have a smaller amount of targets some, then they'll tend to be a smaller, easier
4287.57 -> problem than a larger number for targets. All right, so let's start to think about the
4292.3 -> recursive structure. For the first example, we know in the long run, we should be able
4296.73 -> to derive the answer true from the string that we make. So like, we always say, we should
4301.55 -> encode the arguments to our function into the nodes of our drawing. That being said,
4307.25 -> since in the problem, they told us that we can reuse an element of the numbers array
4311.21 -> as many times as we need, I'm just going to omit that from the node drunk, because basically,
4315.389 -> every node every function call is going to receive the same array. So I'll just list
4319.48 -> the target sum in every node. So I start with seven, and I have to think about how I can
4324.55 -> transition to other nodes, right? How can I shrink the size of this problem? Well, I
4330.179 -> know that I only have, you know, four options I can take right have an option for every
4334.27 -> element of the array. So basically, if I'm at this seven node top level, I can branch
4339.57 -> to some children, and sort of the rule for my transition is you can either take a five,
4343.93 -> take a three, take a four, take a seven. And if I actually take you know those elements
4348.58 -> as a choice, they are going to decrease my current target sum. In other words, seven
4354.119 -> minus five is two, seven minus three is four, seven minus four is three. And of course,
4359.84 -> seven minus seven is zero. So notice, we have a very particular rule from traveling from
4364.49 -> a parent node to some child, I can just carry over this pattern. However, we have to watch
4369.34 -> out right, so let's say I tried to flesh out this note where my target sum is two, if I
4373.909 -> look at the options I have, right, I still have the options of 534, and seven, however,
4378.01 -> none of them are really compatible with this target sum of two, right, what I don't want
4382.659 -> to do is take any of these choices, because that would kind of give me a negative target
4388.17 -> sum. So I can't really flesh out this note two anymore. That is there are no valid options
4392.8 -> for this node. However, some of these other nodes like this for to have valid options.
4397.739 -> So what I can do is take a three or take a four, and of course, I get a one and zero
4401.92 -> respectively. And I'll also do this for the three over here, right? For this three node,
4406.42 -> I can only take a minus three as a choice. Cool. If I look at all of the nodes that I
4412.219 -> have, now, I like the leaf level, it looks like they all sort of bottom out at a base
4416.55 -> case, that is there is no further choices we can take. And I also noticed that some
4421.719 -> of my nodes have a zero in them, if I look at the notes that have a zero in them, they
4426.87 -> actually are a really nice base case, because I basically have found that I can generate
4431.87 -> to the original target sum right away, you can kind of understand the base case, when
4435.94 -> the target sum is zero is you can always generate a sum of zero by taking no elements from the
4442.55 -> array, right? So these zero nodes are trivially solved. And to me, they should return a true
4447.81 -> backup to their parent, right. And it's sort of on the flip side for all of my nodes that
4453.29 -> are not zero. And they also can't break down into any further nodes. Those return false,
4458.61 -> right? Because they kind of have a leftover. And I know there are no possible options I
4462.159 -> can take to reduce that further. So all of these other nodes should return false to their
4466.71 -> parent. And remember what this question is asking, right? It's really asking, Hey, is
4471.28 -> it possible at all to generate the original target sum? And so the logic is when these
4476.82 -> values these Boolean values return to their parent, the parent should just check if at
4481.55 -> least one of them is true. And if at least one of them is true, then that parent should
4485.909 -> also return true. And if I look at this for note, have a tree above it, and that sort
4491.719 -> of answers the question, Hey, can I generate a sum of four using elements of the array
4497.38 -> and you totally can because if you look at the elements of the array, there is exactly
4501.33 -> one for so I can just take that into my son. So I'll be sure to bubble up, you know all
4505.739 -> these Boolean values to their parent. And again at the parent will just make sure that
4510.48 -> at least one of the values that gets back is true. And so if at least one of them is
4515.55 -> true, then the parent itself will also return true. So very reminiscent of some like Fibonacci
4521.46 -> bubbling up pattern as well as our grid traveller, except now we're kind of adapting this for
4526.239 -> some boolean data, but it's really the same structural understanding I have. Cool. So
4531.599 -> that was an example where we said true, right, there is a way to generate the sum. Let's
4537.17 -> look at another example. Right? How do we know the flip side. So if we have this example,
4541.199 -> right, seven, and an array of two, four, in the long run, that's gonna return false, right,
4545.28 -> there's no possible way you can generate that target sum of seven. So the tree for this
4549.489 -> example would look like this. So this is a full tree. Notice that all of the leaves get
4554.449 -> as low as one, but they can't be broken down any further. Like we just said, in our last
4558.96 -> sketch all of these ones, since they can't be broken down any further, and they haven't
4563.119 -> reached zero, they're going to return a false up to the parent. And I know that if I bubble
4568.3 -> up all these false values, of course, a top level call will also return false. Right?
4573.83 -> So it looks like a key insight we have for this problem is, if we find at least, you
4578.3 -> know, one base case that returns true, I know that I can sort of stop early and just return
4583.54 -> true all the way up to my parent, because they're not really asking like, how many ways
4587.61 -> can you generate the target sum? They're just asking, Hey, yes or no, can you generate the
4592.46 -> target sum at all. So that's gonna be a really nice way to implement this code. I think we're
4597.32 -> ready to jump right into the code. So let's do it. Okay, so let's go ahead and implement
4602.8 -> this can sum function. So since we're going to solve it recursively, I think a good starting
4607.57 -> place, as always, is to maybe handle some of the base cases. So when we drew up the
4611.659 -> tree, I noticed that one of our base cases was when the target sum reaches the value
4617.11 -> zero, right? If our target sum is zero, then we have like, basically trivially solved the
4621.869 -> problem, because you can definitely always generate the target sum of zero by taking
4626.83 -> no numbers from the array, right? So I'm going to return true. If ever I reach a zero, then
4631.849 -> Apart from that, I think let's go ahead and work on the recursive scenario. So I know
4636.4 -> that I need to establish some logic where I make a recursive call or a branch for every
4641.4 -> element of the numbers array. So what I'll do is I'll iterate through that array of numbers.
4646.76 -> So I'll use some for let of syntax we'll say let num of the numbers, right. So if you're
4652.03 -> unfamiliar with the syntax, all it does is iterate through every element of the array.
4655.86 -> So for example, let's say I just called the first example for can sum of seven, and an
4661.969 -> array of two, three, if I just console dot log, the num, I'm just going to see the elements
4667 -> printed out of two, three, right, so let's give this a quick spot check. Nice, so just
4671.98 -> iterating through every number of the array, no tricks here. But now that I have that number,
4676.4 -> I need the branching logic. So remember, the logic we use for transitioning from one node
4681.66 -> of the tree to the next, what we did was subtract our current choice of number from the target
4687.5 -> sum, and they basically gave us like the new target sum. So I'm going to express that and
4691.481 -> maybe some variable, so I'll say, Alright, I'm going to generate the remainder by calculating
4696.83 -> the difference between the target sum and the number, right, so I'm just subtracting
4701.57 -> the number from my target sum, that gives me my new remainder, which becomes a target
4705.82 -> sum. Right? So at this point, I think I need to call recursively on can some of our pass
4710.84 -> in this remaining quantity?
4713.19 -> It doesn't need a second argument, still, I'm still gonna pass in the same exact numbers
4717.409 -> array. So I'll still pass in numbers unchanged. I think that you know, the fact that we pass
4723.139 -> in the same exact numbers, right, it's pretty consistent with the way they stated the problem,
4726.59 -> because we can totally reuse the numbers of the array as many times as we like. Cool.
4732.51 -> So now that I'm using our function, again, I'm making the recursive call, I want to think
4736.56 -> about what this call will return. I know at any point in time, my can sum function is
4741.53 -> going to return Boolean. And what's great about boolean data is there's only two possibilities,
4745.219 -> right, either true or false. So I think, based on what we said about the tree was, if this
4751.219 -> call, if that returns true, then I can just ultimately respond with a true right now.
4757.67 -> So I'll write it like this. So this is saying, All right, if we figure out that it is possible
4765.929 -> to generate the remainder now using numbers of the array, then I can return true for this
4770.949 -> larger problem of target some, right? So if I find at least one way that works out, then
4776.949 -> I'm going to do an early return true. And the really important pattern here is we don't
4781.44 -> want to write the LS and then return false. Instead, we'll want to return false after
4786.2 -> the for loop. And so here's the reason why you want to return false after the for loop.
4790.69 -> I only know that after I attempted you know all possibilities and I found that none of
4795.139 -> them worked out Can I actually say that it is impossible to generate the target some
4799.821 -> Right, so I need to make sure that this for loop tries all possibilities of number. Before
4805.041 -> I can say false, the target sum cannot be generated. That being said, there's one thing
4810.11 -> we should add to our code. So if you look at line six, all right, I'm subtracting our
4814.08 -> choice of num from the target sum. And that means that sometimes the remainder might become
4819.5 -> negative. So remembering our tree structure, we made sure to sort of return a false whenever
4824.86 -> we had a sum note, I didn't do any branching. To kind of account for that I can kind of
4830.079 -> bake that into another base case. But this time, make sure it doesn't return true. So
4834.78 -> I'll say, you know, if my target sum is negative, if it's less than zero, then you've gone too
4840.18 -> far. And you can just return false. Here, it's safe to just automatically return false
4846.389 -> if your target sum hits something less than zero, because you've gone too far. And there's
4851.43 -> really nothing else you can add from the numbers array to ever fix that negative target sum.
4856.88 -> Remember, they told us in the problem, that your numbers are always going to be provided
4860.86 -> as positive numbers or just zero, right? So let's go ahead and give this a go. So I'll
4867.659 -> try all of these examples. And we'll see what we get. So I expect a true true false, true,
4876.4 -> false. Cool, so looks like a few of them are working right, it looks like the first four
4880.719 -> are working totally fine. But looks like on that last example, my program still running.
4887.09 -> And if you notice, I chose a pretty large value for em. So kind of like we expect this
4893.34 -> solution has correctness. But it possibly lacks some efficiency, it looks like it just
4897.619 -> finished, the definitely took way too long. And so it's a very interesting example of
4902.429 -> a slow input to can some, because for one, the length of the array is pretty sure I only
4908.27 -> gave two elements here. But it seems like this number as a target sum really affected
4914.23 -> the runtime. So we'll do, let's head back to the drawing board, and talk about the complexity
4919.07 -> of this baseline solution. Alright, so we implemented the brute force for our khamsum.
4925.05 -> But obviously, now we want to make it a little faster. But before we kind of just jump into
4928.28 -> memorizing the solution, let's at least describe the complexity of our current brute force.
4933.76 -> So let's try to visualize this example. So I have a target sum of eight. And my choices
4938.29 -> for my numbers are two, three and five. In the long run, they should return true, the
4942.36 -> visual for the full tree would look something like this, notice that it's fairly large tree
4947.179 -> of forbs and relatively small inputs, right, I only have a target sum of eight, and only
4951.31 -> three choices. So let's try to describe the complexity of this, I'll sort of generalize
4955.32 -> the shape of it, get rid of those numbers. So this is the overall shape of the tree.
4960.349 -> And I know I want to describe my complexity in terms of the input to my function, this
4964.29 -> function has two inputs to the right, I'll say that M is the size of the target sum,
4968.51 -> and n is the length of the array I'm given, I know that both of these arguments definitely
4973.119 -> have an effect on the dimensions of this tree. So like we did in all of our other examples,
4977.92 -> I'll start by maybe analyzing the height of this tree, that is a what would be the maximal
4983.78 -> distance from the top level call to a base case, or in the structure of the tree, what
4988.51 -> is the maximal distance from the root of the tree to the farthest
4991.599 -> leaf. So if you sort of imagine that, we have m as the root of the tree, imagine that along
4998.88 -> the left hand path, we just did a minus one all the way down, right? So we kept taking
5002.73 -> a minus one, right? In the worst case, maybe there's a minus one present in our a numbers
5007.59 -> array. In the worst case, the distance from the root to a base case would be exactly m,
5012.54 -> because you have to subtract one m times, right. So I can basically say that the height
5016.889 -> of this tree is M. And it kind of like we said in our other problems, that means that
5020.719 -> the number of levels is m right. So now that I've identified the height of this tree, the
5025.969 -> move is now to identify the branching factor. That is, how does the number of nodes change
5031.13 -> from one level to the next. Remember that initially, we described this particular tree
5035.65 -> example is having numbers array length of three. And you'll notice that the maximum
5039.96 -> branching factor is exactly three or in general, N, right? because n is the length of the array.
5046.15 -> So for example, if I had three numbers in the array, then a node would have at most
5051.889 -> three children, right? Cuz you have three options to take. We've seen this pattern before
5055.82 -> I have n levels, and from one level to the next, I would multiply the number of nodes
5060.23 -> by n, right? This is the same thing as saying, Hey, we take n and multiply it by itself,
5066.26 -> m times. And this would definitely give us an exponential complexity, right in particular,
5071.29 -> and to the M time complexity, like we always say another great thing about this type of
5075.449 -> diagram is we can also derive the space complexity, right? So what would be the space used by
5080.54 -> the call stack, it would really just be the height of the tree, which we already described
5084.929 -> as M. So overall, our brute force is looking at an end to the M time complexity, and M
5090.89 -> space complexity. So now that we actually have you know those concrete numbers for our
5096.48 -> complexity, let's go ahead and focus on how you can truly improve this So here's again,
5101.07 -> the drawing for this particular example. It's a fairly large one. And typically, you know,
5105.26 -> when you're trying to notice where there is room to be optimized, you might have to give
5108.92 -> yourself a sufficiently large example to see these scenarios. So in this example, I do
5114.11 -> have overlapping subproblems. And in the context of my tree, that means I have duplicate sub
5118.389 -> trees. So I can look at possibly this subtree rooted at three, notice that the root of that
5124.71 -> subtree is trying to answer the question, Hey, can I generate a target sum of three
5130.28 -> using the array? And of course, once you find that answer, that answer is never going to
5134.409 -> change for your target sum of three, right? So I know that all three of these sub trees
5138.949 -> are trying to ask the same problem. And so what I'll do is I'll just cache those results
5144.53 -> in my memo object like we always do. Let's go ahead and work on that. All right, welcome
5150.9 -> back to my code editor. And same stuff, different days. Let's go ahead and minimalize. This
5156.3 -> can sum function, right. Once we've established the brute force through the recursion, then
5161.28 -> memorization is pretty formulaic. Right? Alright. So we'll start by just baking in our memo
5165.88 -> object like we usually do. So if someone calls our function without a third argument that
5169.8 -> is like a top level call, we'll be sure to give them a new object. And I'll, before I
5176.639 -> forget, be sure to pass down this memo object, I'm going to make sure that every top level
5180.86 -> call and it's recursive tree shares the same memo object. But now I need to figure out
5185.88 -> what what should I use to key into the memo object. So here I have two arguments of my
5190.61 -> original function are I have targets summon numbers, what I want to do is try to notice,
5195.08 -> you know, which of these arguments is actually going to directly impact the return value.
5200.73 -> So I know that through the recursive calls like this call over here, the numbers argument
5206.38 -> doesn't change. And so if it doesn't change, then right now, it doesn't really affect other
5210.75 -> return value. So I'd be okay to just use the target sum as the key into this memo. So we'll
5218.17 -> go ahead and do that. So I'll check if my target sum is in the memo, that I've seen
5224.38 -> that sub problem before, so I can just return the stored value. Cool. And now I have my,
5231.48 -> you know, memo fetching logic. But now I need to actually store things in the memo. And
5237.11 -> the trick is, what I want to do is look at all of the return values that were not base
5242.36 -> cases, and I need to now store them in my memo. So I have to return values here, right,
5247.63 -> these two lines, lines, 10 and 14. And so I need to store data into the memo for both
5252.45 -> of those lines, it's as simple as quite literally going into your memo using your key, so target
5259.11 -> some, and then assigning the value we just returned.
5261.87 -> So I'll just write it like this. And this is going to be sort of a hard and fast rule,
5267.489 -> you can always use our for a memorizing a brute force recursive function, right. So
5273.199 -> I'm just going to take exactly the lines or the expressions that I returned in the recursive
5277.95 -> scenarios. And now just store them in the memo. Cool. So let's go ahead and give this
5283.489 -> a shot, this code is looking pretty good. And remember before this last call with an
5288.179 -> input of target, some 300 took notice will be long. But I think now when I give this
5293.54 -> a shot, right, it finishes really quick. So this is going to be a nice optimized solution
5299.199 -> for cancer. Really, most of the work of this problem was done in the brute force. And then
5303.949 -> afterwards, it's really just a minor adjustment to make it efficient. So to wrap up this problem,
5308.739 -> let's go ahead back to the drawing board and talk about the improved complexity. So we
5314.4 -> definitely memorize the heck out of that code. But let's recap by just understanding what
5318.429 -> the new complexity is. So again, we're saying that M is the target sum, and n is the length
5323.67 -> of the array. Initially, we said that our brute force solution had an end to the M time
5327.699 -> complexity, which is exponential. And actually, once we memorized it, we really cut down on
5332.12 -> that complexity, we brought it down to n m times n type complexity. And so here we say
5337.41 -> that the memo is complexity is now m times n, because of the memo object, right? We know
5342.26 -> that the value of the nodes in the tree are just going to be values up to m or their m
5347.52 -> different possible values we can have in a node. However, it now since we are able to
5352.639 -> cache values or cache results inside of the memo object, I'm never gonna have to re explore
5358.599 -> a subtree before M. That being said, I select the branch and times for each of those nodes,
5364.62 -> right. So overall, I have m times n nodes.
5370.16 -> So hopefully that can some problem made some sense, because we want to do now is actually
5373.61 -> carryover a lot of that knowledge to solve this new Howsam problem. So this problem is
5378.07 -> very similar, it still asks us to take in some target some an array of numbers, but
5382.19 -> this time around, we want to do is return an array containing a combination that adds
5386.71 -> up to exactly the target sum. And if there's not any combination that actually leads to
5390.86 -> the target some, then I should just return null. Along with that, if there are many possible
5395.59 -> combinations that can reach the target some that that can return any of those. So you
5399.87 -> probably Already recognizing that this house some problem is very similar in logical structure
5403.93 -> to the can some problem right? instead of returning a Boolean instead now I want to
5408.62 -> return exactly the combination of elements of the numbers array that leads to my target
5414.679 -> sum, so it's a little more involved. That being said, let's take a look at some examples
5418.04 -> to make sure we're on the same page. So let's say someone asked us to calculate how some
5421.82 -> and our target was seven, and our array of numbers are 534, and seven. So there are actually
5427.05 -> a few different combinations that can give you your target sum of seven. One way would
5431.05 -> be to take three plus four, that's one possible answer, another possible answer would just
5435.659 -> be returning an array of seven. So no matter which combination array you return, it'll
5439.86 -> be considered correct in either scenario. Let's say I gave you another example, where
5443.99 -> your target sum was eight, and your choice of numbers were two, three and five. One possible
5448.13 -> solution for a combination is two plus two plus two plus two, right? So I returned that
5453.12 -> in an array, another combination would be just three plus five, notice that no matter
5457.949 -> what we're always looking to get back in array, if it is possible that our target sum can
5463.28 -> be generated. But let's look at the flip side. Let's say that we were given this input, so
5467.76 -> I have a target sum of seven, and my array of numbers is just two and four, the first
5471.829 -> thing we notice is it is not possible to actually generate the target sum of seven. Like they
5476.03 -> said, the problem in this scenario, what you want to do is return Nome sort of symbolize
5480.34 -> that, hey, it's not possible to generate any combination that leads to the target sum.
5484.48 -> All right, I think it's important that we think about one more scenario. So let's say
5489.13 -> that I was given a target sum of zero. And we were given really any array of numbers.
5493.26 -> In this case, I just gave us one, two, and three, we already know that in our previous
5497.01 -> problem, we had to return a Boolean result, we use the target sum of zero as a base case.
5501.489 -> And so what I want to think about now is how is the target sum of zero just trivially solved?
5506.159 -> Well, if I want to know the combination that sums to zero, and that's really just the empty
5512.429 -> combination, right. So I think the logical result here is to return an empty array, when
5517.5 -> your target sum is zero. Remember that an array represents a combination. And if I have
5521.579 -> an empty array, that means I take no elements into my combination, right? If I summed up
5526.04 -> all the elements in that empty array, it would indeed have a sum of zero. So that's going
5529.82 -> to be a really important facet that we need to encode into our logical tree that we draw
5533.54 -> next, as well as our code, right. So let's take a look at how we can structure the tree
5537.65 -> and try to take a step toward really understanding how we can implement it in some code. So let's
5543.219 -> say we're stepping through this particular example of target sum of seven, as well as
5546.57 -> an array of 534, and seven. So the full tree and really the same tree that we drew last
5551.739 -> time would look something like this. from the get go, I see that I have some scenarios
5556.36 -> or some nodes where I reach zero, which means that it's definitely possible to generate
5561.51 -> the target sum. That being said, How can I get back a valid result, right, so I need
5566.381 -> to return an array. So you sort of reframe this problem in that, all right, all of these
5571.02 -> base cases that have a target sum of zero, they are trivially solved because they are
5575.79 -> combination would just be the empty array. Right. So for now, I'll just kind of trace
5580.55 -> through how one of these base cases would return. So I know that this particular zero
5584.76 -> is going to return an empty array. And like we always say, when it returns, it's really
5588.33 -> returning that information to its parent, right. So this array sort of bubbles up to
5592.489 -> its parent. And now what I want to do is actually manipulate this return value, I want to add
5597.81 -> something to it. So I want to really put the number that brought me to that zero in the
5603.04 -> first place. And that would really be the choice of four. So notice along the edge,
5606.989 -> I have them labeled with a minus four, meaning that, hey, I took a choice of four. And I
5611.639 -> want to actually add that choice into the current array. So I'll just really push it
5615.65 -> into the right, of course, I actually don't need a negative sign that was just for the
5618.55 -> sake of understanding the math here. But now that my call to how some of four is returning
5624.57 -> an array of four, that array bubbles up to its parent. And of course, now I need to push
5630.05 -> that edge that I took, which would be that three, so that three gets pushed to the rear
5634.75 -> as well. And if I look at what I'm seeing right now, it looks like above the seven,
5638.699 -> we have an array of four, three, which makes sense, because you can totally generate a
5643.26 -> target sum of seven by doing four plus three, awesome. So four comma three would be a valid
5648.76 -> answer in this particular problem. But like they said, you could return really any valid
5653.07 -> combination if one exists. So let's say we retrace through this far right base case,
5658.03 -> we know that this zero is going to return an empty array, that empty array will be returned
5662.53 -> to the parent. But we also have to add the value according to the edge, right? So I would
5668.181 -> press seven in this array. And that makes sense because I could generate the target
5672.3 -> sum of seven by just summing up a loan seven. So that's still good to go. So I'm feeling
5677.9 -> pretty good about how we can return a valid combination if one exists about let's say,
5682.41 -> there are some options that we take that don't work out. So for example, let's say we explored
5687.52 -> this node first, right? This was the first base case we had and logically in the space
5691.62 -> of our code, it really would be right. So I know that this node can't really branch
5696.13 -> out any further. So it's sort of a dead end. This is a node that should work Turn no meaning
5701.949 -> that, hey, there is no way I can generate a target sum of two using elements in the
5707.77 -> array. Or if you look at the elements of the array, they're all too big, right? So I know
5711.619 -> that this base case is going to return null. But along with that, if you look at the next
5715.51 -> base case, we would hit it would be this one, which also returns Now, if you look at the
5720 -> next base case, to the right, that's actually an affirmative one that should return an empty
5723.13 -> array. So it's kind of reasoned out how these return values should be considered at their
5727.71 -> parents. So I know that both of these values, but to the left of the node four would return,
5733.42 -> right, so it kind of bubbles up a little bit. But for the array on the right hand side,
5737.909 -> I would also have to push the edge that I took, which was a four in this scenario. So
5742.73 -> now I'm sort of comparing you know, the nollan, this four, or really, I'm comparing all of
5747.07 -> the branches that I take from a node. And if at least one of the branches gives me back
5753.04 -> an array that I know that it's possible, right? So basically, in this scenario, the array
5758.409 -> of four actually wins out over the null, right, I know it's possible to generate a four. And
5763.199 -> I'll just continue this process, right, now I can return these two values to its parent.
5767.67 -> So let's I'm considering them at seven. In the same way, I would have to add the edge
5772.8 -> I took, which would be a three on this array. And then from there, I know the array would
5776.96 -> always override the null, right? So I could just ultimately return this four comma three.
5782.13 -> And a really nice pattern in this code is, as soon as we find a winning path through
5787.55 -> the tree, or that is we find a combination that creates a target sum, we can actually
5792.21 -> return early because we don't need to really travel through the rest of this tree. We don't
5795.62 -> need to explore any other options because they're happy with at least one combination.
5799.88 -> Right? Awesome. So again, to recap, the punchline is for this tree, I have the information for
5806.35 -> a combination, encoded as a path through edges of this tree, right, so I've looked at this
5811.36 -> path I have highlighted in yellow, I see that I took a three followed by four. And that
5815.07 -> eventually led to a zero. So I know that one valid combination would be four comma three.
5819.889 -> All right, at this point, I'm feeling pretty good. Let's go ahead and work on the code
5823.29 -> for this now. All right, here we are back in my code editor, let's work on solving the
5828.059 -> house implementation. So the codes going to be pretty similar to the cancer problem, it's
5833 -> the really the whole point, we're just kind of going to finesse the return date over here,
5837.05 -> we're gonna have a very similar base case, like we said, in the tree diagram, whenever
5840.5 -> we have a target sum, that's zero, we have trivially solved the problem, because I could
5844.56 -> just return an empty array. And a similar way, what we can do is also have a separate
5849.719 -> base case, if our target sum ever reaches a negative quantity, then I'll just return
5854.461 -> null. That's because it's never possible to generate a negative target sum, right? Remember,
5858.489 -> in this problem, the array of numbers is only going to be positive ones. Cool. We ended
5864.179 -> up writing very similar base cases for our last problem, except now instead of returning
5869.04 -> true, we return an empty array. And instead of returning false, we return nil. Nice. So
5873.4 -> let's go ahead and get the branching logic. Right, how do I want to make my recursive
5877.989 -> calls, I'm going to need to make a recursive call for every element of the numbers array.
5883.36 -> So I'll say, let num of numbers, we'll just iterate through every number of this array.
5889.07 -> And I'll go ahead and do the same logic as last time. So I'll subtract the number from
5893.27 -> my target sum, they'll give me my remainder, right? So remainder equals target sum minus
5897.969 -> mine number. Cool. So this remainder is now what I want to find the combination for, right?
5903.659 -> So here, I would make my recursive call. So how some pass in the remainder. And the second
5909.329 -> argument will stay exactly the same. I'm going to pass along the same exact numbers array.
5913.76 -> I don't need to remove anything from the numbers, right? Because they tell us in the problem
5917.489 -> that we can reuse the elements
5919.13 -> however we see fit, right. Cool. So now I need to really think about what type how some
5924.1 -> returns. So this problem is interesting, because we basically have two different types, right,
5927.95 -> we could get back in array of some elements, if it is possible to generate the remainder,
5933.699 -> or we can get back know, when it's not possible to generate the remainder. I think no matter
5938.47 -> what I get back, I'm just going to save into a variable. So I'll say console, this is the
5943.03 -> result for the remainder. I'll call it remainder result. Cool. So I know that in the context
5949.5 -> of like how I thought about this logic in terms of the tree, I wanted to basically do
5954.9 -> an early return if I found a valid combination. And so I'll go ahead and check you know, if
5961.079 -> the remainder result is not No. So the implication if I enter this if statement, that means that
5967.82 -> it is possible to generate the remainder. And so what I can do is just return early
5973.36 -> and I can return of basically almost the same remainder is also the same array. However,
5979.71 -> I need to make sure I include the element that I took recall from the diagram, whenever
5984.099 -> we had a recursive call that returned in array, the parent had to also add the number that
5991.159 -> it took transition to that recursive call in the first place, right. So in the context
5995.909 -> of the tree, I had to put the labels of the edges into the array here. means I have to
6000.599 -> put the num into this array. So some syntax I can use for that. So use some spread operator.
6006.47 -> So I can copy all the elements of the remainder result into this new array. But I'll also
6012.659 -> add on the number I just took, right. So all I'm doing in this return line is, I'm returning
6017.829 -> basically the same array that I get back from my recursive call, with the number added to
6022.75 -> the end of it. Let's say you're unfamiliar with this syntax, I can preview it really
6026.1 -> quick. It's pretty neat. Let's hop into the, you know, repple. So let's say I had some
6030.679 -> array, let's say it was this array of just a, b, c, sort of an isolated example, you
6037.079 -> can use the spread operator, which is the three dots here to like unpack an array. And
6041.199 -> you can sort of say like, new array equals bracket. So this gives me a new literal array,
6047.86 -> I can spread out the elements of the old R, right? So if I do that, the new array just
6052.95 -> has all of those same elements, vi, capitalize that and properly, cool, we can extend that
6058.92 -> syntax, right. So I still have that array of ABC. Let's say I had another variable,
6063.88 -> we'll call it other array, what I can do is create a new literal array, copy all the elements
6069.099 -> from our using the spread operator. And then I can comma, separate any additional things
6074.03 -> I want to add while I'm here. So let's say I added a z. So if I look at my other array,
6079.23 -> now, it contains ABC, and also this new element z. So it's all I'm doing on this slide, right?
6084.949 -> I'm returning the same combination, that my recursive call gives back with the number
6090.55 -> that I took are added to the end of it. Cool. So what's really nice about this code is it's
6095.73 -> very reminiscent to our previous code, because we have an early return. So if I find at least
6099.79 -> one way, to generate the target sum, I can just go ahead and return that first way that
6104.57 -> I found, right, they say we can return any valid combination here. But let's say this
6108.54 -> for loop finished, and we never found a valid way to generate the target sum, then after
6113.989 -> the for loop, you can just return null, because it's not possible not to generate the target
6119.78 -> sum, apparently. Cool. So this code is looking pretty good. I think let's let's give it a
6125.59 -> shot. It goes without saying no, sometimes the remainder right will become negative.
6130.989 -> But we already handled that with this nice base case, right. So let's test this code.
6136.869 -> So I already have some example outputs over here. So looks like our first few examples
6143.6 -> are working. Obviously, our last one seems to take quite a long time. So I'll just kill
6148.89 -> this program early. But before we talk about how to optimize for this larger example over
6152.809 -> here, if you look at these other examples, what I can see is this array of 322 does add
6159.369 -> to seven. So that's looking good. This array of four, three, also adds to seven. So that's
6165.41 -> good. We already saw that this example of seven and two for now, that's your return
6169.93 -> No, because it's not possible generate that sum. And it looks like the array for the eight
6174.91 -> example is also good to go. So notice here, there are also some other arrays that we could
6179.91 -> have returned and would still be considered valid output, the exact combinations that
6183.54 -> we get back are really just dictated by the order that we happen to iterate in the array.
6187.5 -> So for example, let's say I switch things around here, like I did 352, it's really the
6192.079 -> same array, just in a different order, we'll probably get a different answer for that particular
6197.199 -> one, right? So I'll run this now, notice I get 233. But either case, that still sums
6202.64 -> to eight, so we're
6203.679 -> totally good to go. Awesome. They bring this back to the original code. And yeah, it seems
6208.83 -> that now the limiting factor seems to be the speed of our solution, right? We've been here
6214.73 -> before. And so let's go ahead and talk about the complexity of it. So we know that the
6220.179 -> time complexity for this is almost identical to what we did in the cancer problem, which
6224.84 -> is conveniently over here. But sort of refresh. By now you recognize that all right, the time
6231.199 -> complexity should be described in terms of like the recursive tree, let's let's lay down
6235 -> the foundation. And we already know that M, we're going to call the target sum. And let's
6240.469 -> also say that n is the numbers dot length. Cool. And so if I think about the number of
6247.199 -> recursive calls that I'm going to make, it's really the same as last time, right? So I'm
6251.51 -> going to say that, hey, the time of this function seems to be Oh, and I have an exponential,
6259.4 -> the base of the exponent is the branching factor, which is the length of the array.
6263.889 -> And then the depth of the tree would be the exponent, which is just m, right. So this
6269.11 -> was the same time complexity as last time for a brute force writes M to the M power.
6273.32 -> However, it looks like we have some additional time that we consume in this function really
6279.15 -> coming from this expression, right. So if I look at line nine, in particular, this line
6285.25 -> creates a copy of an array. So that will actually take sort of a linear number of steps to copy
6291.409 -> over every element of an array right. So I will have to consider the cost of this operation.
6296.639 -> It sort of under the hood like iterates through the remaining result, I know that the maximal
6303.119 -> length of the remainder result I can get back will be at most m, right? Remember that remainder
6308.52 -> result in the worst case is going to be in array, the longest that array could be is
6313.329 -> exactly the target sum, right? Imagine that we had the most simple, you know, sort of
6317.86 -> a combination to generate the target sum. If it was just a bunch of ones, right, my
6322.09 -> target sum was 50. And I had an array filled with one, then I can just do one plus one
6328.03 -> plus one plus 150 times to generate the target sum. So in the worst case, this copying operation
6334.829 -> will take m steps, I need to do that for every recursive call. And so if I have this many
6341.77 -> number of recursive calls, in addition to that, I do an M operation, you just multiply
6346.489 -> that M, right. So the time complexity for this brute force so far, is n to the n power
6352.32 -> times M. Really, the thing we want to optimize away is this exponential part, right? So I
6357.27 -> really want to focus in on that. But while we're here, let's go ahead and talk about
6359.76 -> the space of this brute force. So the space, there are things to consider the terms of
6365.239 -> the stack space, it's going to be the same as last time, so it's going to be o of m.
6369.349 -> But think about any other space I use up I guess you should also consider the array that
6375.26 -> you return back, whoever we know that we're going to return back like the first array
6380.239 -> that we find all the way back up to the top level call. And the combined length of all
6385.73 -> of those arrays that we return is really just going to be in worst case, M. Right. So I
6391.1 -> would still say that the space complexity for this function is just m right now. Cool.
6397.28 -> Obviously, let's work on optimizing this time complexity. So this was the brute force. So
6402.62 -> brute force. And you guessed it, and there is definitely some people get some problems
6407.79 -> in this brute force. So we'll just memorize it away. So very formulaic stuff, most of
6413.46 -> the work is always the brute force. So I'll start with my empty mental object. And like
6418.03 -> we always say, we'll use the target sum as our key. So if I've seen the target sum before,
6422.94 -> that is if it's in the memo, then I'll return what I have stored in the Mo. And at this
6430.829 -> point, what I want to do is make sure I pass along the same memo object to all of the recursive
6434.969 -> calls. That way, they can benefit from any information or any sub problems that I worked
6438.67 -> on over here, right in this stack frame. Cool. At this point, I need to make sure that I
6443.53 -> take all of the return lines that I had before, and I store them in the memo, right? So the
6450.11 -> trick here is I can say, memo of target. So I'm going to save that array. So notice that
6457.739 -> now the values in the array, or sorry, the values in the memo object are going to be
6463.13 -> arrays, because that's the return value of this. It's either going to be an array or
6466.23 -> not right? So I'm just going to complete this return value by returning what I just put
6470.71 -> in the memo.
6473.909 -> And in a similar way, I also want to memorize this late return. So we'll also be memo of
6479.429 -> target sum equals No, I can just still return No. Cool. So with that change, right, it's
6488.11 -> very, very straightforward change. Let's see how this does. Now. I know that for this last
6494.14 -> example of how some 300, it is not possible to generate the sum of 300 using just sevens
6499.25 -> and fourteens. So should we get an all for that one. So let's give that a shot. Nice.
6504.89 -> And there, we have a really quick running a know of that very last example. So we definitely
6509.73 -> had an effect on the runtime here. Let's talk about how we changed up the complexity of
6515.119 -> this code. So that was a brute force. Let's talk about the memoized version. So it's good
6522.4 -> with the time over here. So we know that in terms of the time complexity from the number
6527.48 -> of recursive calls that we make, it's the same story as our last problem, right? We
6531.65 -> just have to consider any other time operations, which would still be this array copying. So
6537.219 -> but in just terms of the recursive calls that we make, it's going to be n times m, recursive
6543.409 -> calls. And then in each recursive call, I need to do this copying pattern, right copying
6548.51 -> over the contents of an array, and the array will be at most m elements long. Alright,
6553.52 -> so if I have this many recursive calls, and each recursive call needs to make an M length
6559.909 -> operation, now I just multiplied by another m term. So right now this looks like n times
6564.929 -> m times m, which is the same as n times m squared. And I'll talk about the space complexity.
6572.619 -> So the space complexity will be at least as big as our brute force, so it's going to be
6578.36 -> at least o of M, above, now we have to consider all the space we use up in the memo object.
6585.42 -> So I think about the keys of this memo object, the keys are just going to be all unique values
6591.2 -> of target sum, right? Because I just literally use target sum as a key on all these lines,
6596.42 -> right? But then everything about the value or the values are going to be Pretty chunky
6600.6 -> now, because sometimes of value but in the object is going to be an array. And I already
6607.04 -> said that the maximal length of any of these arrays that I returned is going to be of length
6611.349 -> M. Right. So to me, it's now the case that your space complexity comes from mostly your
6616.969 -> mental object, which is going to be of size, m times m, right, because you have m keys.
6623.44 -> And each key has a value, which is, at worst going to be an array of M elements. So it's
6628.219 -> m times m, which is m squared. Cool. Notice that we definitely cut down on the brute force
6636.56 -> implementation, especially in terms of the time complexity, obviously, there was a little
6640.65 -> trade off that we made here. But now we're able to run our house and function in a reasonable
6645.51 -> amount of time. So to wrap up this problem, let's go ahead and hop back into the drawing
6650.15 -> board. Alright, now that we coded the house and prom, let's wrap it up by actually analyzing
6654.67 -> the complexity of it. So recall that for our house, some problem, we have multiple arguments,
6659.289 -> right, so I need to describe them. So I'll say that m is my target sum, and n is the
6663.3 -> length of the array. Remember that the array contains the choices we can take. And our
6667.42 -> baseline solution, that is our brute force recursion had an exponential time complexity,
6671.89 -> right? It was kind of in the form of n to the n power times M. And after we actually
6677.38 -> optimize it using the memorization strategy, we actually reduced it from exponential down
6682.94 -> to a polynomial time complexity. In particular, our new time complexity was n times m squared.
6688.57 -> So really important fact about our memoized have complexities, it is no longer exponential.
6693.83 -> Please recall that when we have m squared, that is not exponential, because the exponent
6698.909 -> is a constant number. Cool. If I look at the the space complexity for this, I had to actually
6704.67 -> pay a little more cost in terms of the space because of the memoized object. But that space
6710.429 -> complexity we use up is still not exponential, right? Still a polynomial or quadratic complexity.
6716.369 -> So I'm still satisfied with it overall. So we definitely prefer this memo is complexity
6720.86 -> over the brute force. All right, that was the how some problem but now let's go over
6725.3 -> one more variation of it.
6728.23 -> In this problem, I'll still give you a target sum as well as an array of numbers to choose
6732.679 -> from. But this time, what you want to do is return an array containing the shortest combination
6737.199 -> of numbers, that adds up to the target sum. And along with that, if there's any ties for
6741.71 -> the shortest combination, you can return any of those shortest ones. So from this description,
6746.5 -> you probably recognize that this is similar to our last few problems. But now they're
6750.12 -> asking for a little bit of an optimization, right? So I want the shortest sums, that means
6754.88 -> an array containing the least amount of numbers, but it still adds up to the target sum. So
6759.87 -> we're really just going to add on top of our previous understanding, let's take a look
6762.66 -> at a few examples. So let's say I gave you this example. So here, my target is seven,
6767.489 -> and the numbers I choose from are 534, and seven. So there are a few different ways you
6771.86 -> can generate your target of seven, one way would be to do three plus four, that's one
6776.44 -> combination. But another way would be just take the seven because notice that seven is
6779.98 -> actually a member of the array. And here I want to choose the smallest array, which would
6784.88 -> be just the lone seven. So that would be the expected result for this one. In a similar
6790.119 -> way, let's see, I gave you a target sum of eight. And your choice of numbers were two,
6793.76 -> three and five, there are plenty of ways to generate eight, you can do two plus two plus
6797.659 -> two plus two, or you can do two plus three plus three, or you can just do it three plus
6802.099 -> five. And here, I want to return always the shortest combination, which would be the three
6807.059 -> plus five. So that would be the result over here. Cool. So we definitely have some awareness
6812.429 -> that Alright, this problem is similar in structure to our previous one where I need to sort of
6816.59 -> generate a way to make the target sum. But now I want to really specify the shortest
6822.52 -> array, right? So let's try to visualize this as a tree. Like always, let's say we're stepping
6828 -> through this example, with the target sum of eight. So we know that the full tree would
6831.409 -> look like this. And we've seen this tree before. But now what we're trying to do is come up
6835.5 -> with a different process for the value your return, right, it's no longer going to be
6839.849 -> valid to just return the first way that I find to generate the target sum. Now I want
6844.159 -> the best way. So let's start coming up with the strategy for this. At this point, we know
6848.86 -> that we can definitely implement some logic in our code where we return an array up our
6852.96 -> recursive calls, we did that in the last problem. So here, we'll sort of use some abstraction
6856.79 -> to assume some return values. So let's say that I'm bringing this problem down, my top
6861.59 -> level problem asked me what's the best way to generate eight. But I see that along that
6865.89 -> path, I have to find the sub solution for the best way to generate six. So let's say
6871.329 -> we're rooted at this subtree. And we know that there are a few ways we can make six,
6876 -> right? If I look at this subtree rooted at six, there are two zero base cases downward,
6880.539 -> right? So if I look at the first one, I get this path, I know that my sub tree over here
6886.38 -> will be able to know that all right, one way to generate six would be two plus two plus
6891.309 -> two, right? We kind of implemented that logic in the last problem. But now what I also want
6896.04 -> to do is consider any other paths in case they end up being shorter. Obviously, I know
6900.17 -> That this path of three 300 return upward. And now I have to kind of make the decision,
6905.639 -> you know, between these two options for generating six, I should just prefer the shorter one.
6910.81 -> So that means it's sort of two to two versus three, three. And obviously, the three, three
6915.19 -> wins because the array length is shorter. And if I pause right here, and I looked at
6918.77 -> what the diagram is saying, it does make sense that above the six, we have three comma three,
6923.46 -> because that is the shortest way we can ever make a six, right, just three plus three.
6928.31 -> But now I can return this sub array to my parent. But if I do that, I should also include
6933.619 -> the value of the edge along that path, right? So I also included the two over here. Now
6939.67 -> this makes sense, because one way I can generate eight is three plus three plus two, right?
6944.19 -> But we know that's not the optimal way. Let's say we step through this a little further,
6948.469 -> let's say we took the same sort of process for this other child of five. So I know that
6953.699 -> to generate five, there are three options according to the subject, right? Because I
6957.36 -> see three different leaves that are have zero inside of them. And so I know that for these
6962.739 -> paths, they would work out through their own sub arrays. And here, I really just want to
6968.38 -> choose the shortest one, right? So obviously, the lone five wins out over here. So that'd
6973.67 -> be returned all the way up to that five node. But then that five node will return it to
6978.429 -> its parent, then, of course, I need to include the edge that connects the eight to the five,
6983.57 -> right? How did I transition to that five in the first place, so I go ahead and just add
6987.282 -> it in. And now the root node over here has a decision to make, which one does it prefer
6990.75 -> between 332 or five, three, it'll just choose a shorter one. So the five three should win
6996.78 -> out over here. And I just need to continue this process for any other branches in my
7002.239 -> tree, right? I can't return early and this type of problem because I need to find the
7006.789 -> optimal way. And I can't be sure until I've tried every possibility.
7011.199 -> Cool. So
7012.32 -> it looks like our overall logic is we want to explore and find any ways to generate our
7017.989 -> target sum. But then when we find a way that's shorter than our currently tracked way, we
7023 -> can just replace it, I'll continue that process through the entire tree. If I checked every
7027.75 -> possibility and keep replacing what I consider the shortest. By the end of us exploring every
7032.599 -> branch, we would have the absolute shortest. Alright, I think we're ready to hop into the
7036.84 -> code. Alright, programmers, welcome back to my text editor. Let's work on coding this
7041.23 -> one out. So here I have some initial examples that we'll use to test our best sum function.
7046.56 -> I noticed for this last one, right, I get my initial target sum of 100. And the best
7050.989 -> way to do that is obviously just take a bunch of 20 fives, right for 20 fives. What else
7055.239 -> I want to bring our attention to is this third example over here. So my target sum is eight.
7059.26 -> And my choice of numbers are one, four, and five. The optimal answer here is four and
7063.159 -> four, right? That is the shortest way to generate at a common mistake I see a lot of students
7066.8 -> make is sort of assume that the best way to generate a target sum always involves taking
7072.869 -> the largest choice of number as many times as we can, right? That is not true. If you
7077.28 -> took a five in your your sum, then you would have to take three ones in the long run, which
7082.54 -> is not going to work out to the shortest answer, right. So it's not the case that just taking
7087.21 -> the biggest choice of number always yields the target sum in the shortest amount of numbers
7092.59 -> overall. So don't fall into that trap. Which means that we have to do the full tree exploration,
7096.889 -> right, we have to do that in exhaustive sort of search using our recursive code. So let's
7101.97 -> start with that base case, like we always do. So bring this a few times, but still right
7105.809 -> here. So if the target sum is zero, then I have trivially solved the problem. So I can
7110.139 -> just return an empty array, right? That is the exact single combination and the shortest
7114.98 -> combination that can generate the target some cool along with that, let's handle that scenario,
7120.469 -> if our target sum goes too far downwards, if it's less than zero, so if the target sum
7124.869 -> is less than zero, I'll just return null, meaning that it's not possible to generate
7128.73 -> that target sum. And now we'll use our branching logic. So I'm going to iterate through all
7133.409 -> choice of numbers will say for let num in numbers. And like we always do, I'll go ahead
7139.77 -> and create my remainder, which I know would be the difference between my target sum, and
7145.82 -> that choice of number. Right? So what I'm doing is I'm choosing the number that decreases
7149.32 -> my target sum. At this point, I would make my recursive call right with that remainder,
7154.309 -> passing in the same numbers array. But now I have to do some thinking, I know that all
7158.81 -> right, best sum is either going to return to me a combination or an array, or it's going
7163.9 -> to return null, so I'm gonna have to kind of differentiate between the two. So over
7168.65 -> here, maybe I'll just save this as a result. I'll call this my remainder. Let's say combination.
7178.02 -> And if this remainder combination is not null, then it can do some other logic, right? So
7182.909 -> I'll say if the remainder combination is not equal to No, then do stuff. So if I enter
7189.66 -> this if statement, then that means it is possible to generate the remainder. And exactly what
7196.489 -> remainder combination is, is an array containing the short This way, I can generate the remainder.
7202.6 -> Cool. So if I enter this if statement, then I also have a way to generate the full targets,
7207.869 -> right, I can just take the remainder combination, copy the elements over from it like we did
7212.59 -> last time. And also add on the choice of number I just took, right. So this would be now a
7218.11 -> complete combination for target sum. So I'll just say that as a regular combination. Cool.
7226.059 -> So so far, this is very reminiscent to our last one. And we need to work in some more
7231.07 -> logic relevant for this problem, that is a very, very important characteristic that we
7235.94 -> needed to implement in the tree was, I need to sort of choose the shortest combination,
7241.17 -> RAMs, I'm going to need to work in that logic. So here's what we can do, I know that I need
7246.849 -> to compare basically, all of my branches together, all of my recursive calls together, and pick
7252.51 -> out the shortest combination. So I know that this for loop is a piece of code that sort
7257.5 -> of iterates and attempts, all of my branches. And so outside of the for loop, I'm going
7262.929 -> to need some outer variable, I'll call it my shortest combination. And over time, I'll
7268.46 -> just keep updating this variable if I find a combination shorter than my current shortest
7273.199 -> combination, right? So I'm gonna initialize this to null.
7277.969 -> And the reason is, imagine that we set up this shortest combination of variables, no.
7283.389 -> And then we iterate through the for loop. And there isn't even any way to generate the
7288.9 -> target sum. So this for a loop finishes, and shortest combination would still be no. And
7294.28 -> that'd be great to return, right? Because in this problem, they said, if it's still
7298.48 -> not possible to generate the target sum at all, you should still return null. So this
7302.37 -> is a good initial default value for shortest combination. But now I need to do my update
7308.44 -> logic. So here on line 11, I've actually created a combination, that gives me the target sum.
7313.719 -> But now I need to check, right, so I'll say, if the combination is shorter, then the current
7321.449 -> shortest that I need to update it. So let's translate that into some code. Just use an
7327.52 -> if statement. And I'll start by checking if the combination I currently have, which is
7331.159 -> an array, right? I check if that is less than in length, then the shortest combination variable.
7340.97 -> And really, it looks like shores combination, it starts as null. So I need to fix this code
7344.659 -> a little bit. But if I update it with a valid combination, then it's going to be an array,
7349.22 -> right? So I'm really checking the length of the arrays here.
7353.58 -> So
7354.58 -> what I want to do is assign my shortest combination with the combination that is now shorter,
7360.44 -> good. So this means the shorter combination wins out and gets to stay, right. If I look
7365.23 -> at this code, it needs a little bit of work, because the first time I find some combination,
7370.07 -> I know that I be comparing that array length to no dot length, right? Because shortest
7376.119 -> combination starts as null. And I can't do null dot length in JavaScript. So I'll need
7380.489 -> like a nice or clause here. So I'll say, if it is the case that your shortest combination
7386.57 -> is equal to No, then you can go ahead and replace it. Cool. So this sort of check over
7393.929 -> here is going to make sure that I automatically replace this null value with the first valid
7399.8 -> combination. Even if right now it may not be the shortest, right later on, I'll compare
7405.02 -> that combination I have stored to some possibly shorter combinations. Cool. So this code is
7410.5 -> looking pretty good. Let's go ahead and give it a little test run. Nice. Notice that and
7416.849 -> on occasions where we call besam, with a remainder that is negative because remember, sometimes
7421.639 -> we subtract a number that is maybe too large, that's okay. Because that will actually bottom
7425.929 -> out at a base case and return null, which we sort of check for explicitly in this if
7430.239 -> statement. Cool. So let's give us code a run node best some. So I get an error over here
7437.659 -> looks like I get maximum call stack size exceeded, which means that we didn't really hit our
7442.96 -> base case. So there's some work to be done here. So let's take a look at this code. So
7447.73 -> if I take a look at this code, it's a very, very small typo. It's kind of unfortunate
7451.289 -> that it breaks the entire code. But really, I messed up when I iterated in the for loop
7456.65 -> over here. So here I wrote for letting them in numbers, that would actually give me the
7463.349 -> indices of the array. So they'd be like 0123. Whereas I want the elements of the array,
7468.96 -> right? So instead of in any of over here, so that's on me. So with that small change.
7473.969 -> Let's give it a run now. Yeah, it looks like we're passing these first three examples,
7479.23 -> we have 735, and four, four, notice that the order among the elements in the combination
7484.679 -> doesn't really matter too much. But looks like we're definitely a little too slow on
7488.83 -> this last example, over here, right? So obviously, we know the move is to memorize this because
7495.679 -> we have the brute force recursion. But before we do that, let's just talk about the complexity
7499.75 -> of This. So this code is going to be very similar in complexity to our last how sum
7504.9 -> function, sort of compare the two, it's almost the same code, right? But do these side by
7510.84 -> side, right, these two functions look very, very similar. So we know in the long run,
7514.769 -> we probably have the same or close to the same complexity. Let's be methodical. So we
7519.829 -> know that over here, we always like to say that M is the target sum. And we'll also go
7525.13 -> ahead and say that n is the numbers dot length. So we just did the brute force. And our brute
7533.9 -> force shouldn't be the same story as last time. So talk about the time. So the time
7538.77 -> of the brute force is going to be some sort of exponential, right? If you remember that
7542.73 -> tree drawing, in general, the exponential number of nodes in a tree will be the branching
7548.9 -> factor to the height power. So not the branching factor here is n, right? I branch for every
7554.3 -> choice of number, and then the height of the tree would just be the target sum. So that's
7558.48 -> n to the M.
7560.86 -> But along with that, we also have some additional operations, right? If I look at this for loop,
7565.84 -> so this for loop gives me the branching factor, right. But then I also do this operation on
7571.389 -> line 11, which is copying over the array of remainder combination. And that array in the
7576.15 -> worst case will be of length m, right? The largest sort of combination, give me back
7582.179 -> is a combination that is just filled with a bunch of ones, right? If my target sum was
7586.34 -> 50, the longest combination possible would be a bunch of ones, so 50 ones in an array.
7591.829 -> So what I'll do is, I'll say that for each of these n to the M power calls, you also
7597.079 -> have to do a linear operation in M, right? So like before, it's n to the m times m. Cool.
7603.269 -> And then the space complexity is sort of interesting in this one, because we're maintaining some
7608.239 -> values. So look at this. So I talked about the space complexity, just from the stack
7613.949 -> space, it would just be the heights of like the recursion. In other words, it would be
7617.329 -> m over here, right? So I know it's going to be at least M. So I'll jot that down. But
7622.57 -> then we have also like this variable on line five, right? So I know that over time, I'm
7628.71 -> going to be storing an array inside of this variable. And this array is going to be in
7632.949 -> the worst case, m in length, right? So what I'm saying is, every recursive call would
7637.82 -> have to have its own shortest combination variable, right? If the shortest combination
7642.13 -> variable is going to be an array of length m, that means I have an array of length m,
7646.87 -> for every recursive call right? Before I bought them out, at like my final base case. And
7651.54 -> so also, the space complexity here is m times m, which we know is the same thing as m squared,
7657.64 -> right? And sort of the reasoning is, your maximal stack depth is still m like last time,
7662.909 -> however, now you need to have those stack frames, you need to store in array, right
7667.15 -> as you recurse. Nice. So really, the limiting factor for us right now is going to be the
7672.659 -> time complexity, which is exponential. And so like we always say, let's go ahead and
7676.809 -> memorize this. So memorization pretty trivial right now, right? You've done it many times.
7682.369 -> So I'll just begun my initially empty memo object. And I'll check, you know, if my target
7687.26 -> sum is in the memo, then I should actually return the stored value. So return memo at
7693.719 -> Target sum. So now that I have my memo checking logic, what I also want to do is add the memo
7698.389 -> storing logic, right. So I need to just go to my return value and store it in the memo
7703.55 -> before I return it. Notice that the return value right now, it's no longer inside of
7707.289 -> the for loop, right in the last problem, it was in the for loop, because I can return
7709.96 -> early. But this time, we're going to return at the very end, so I'll replace it over here.
7714.739 -> So I'll say, for here, the memo at Target sum should be stored with the shortest combination,
7721.27 -> I can still return the shortest combination. And before I forget, let me go ahead and pass
7726.159 -> down the same memo object by reference. Alright, nothing too much to that. Let's go ahead and
7733.09 -> try this last example. Now. Give it a shot. Awesome. And there we have it. We have 425
7738.94 -> in this last example. And that is the best way to generate a 100 is pretty evident that
7744.469 -> we cut down on the runtime. So let's talk about the memoized complexity embolized. And
7750.719 -> so the time is obviously much faster should not be exponential. But if I take a lay of
7756.04 -> the land, I know that now that every target sum is going to be a key of the memo object.
7762.71 -> And target some is really just a number, right? So if my target time is 50, then I basically
7767.46 -> have 50 different keys I can never store in the memo object. So if I have m different
7772.41 -> keys in my memo object, I know that I won't be exploring any full duplicate sub trees
7777.909 -> for each of those keys. However, I will slap the branch for every number in the array,
7783.789 -> right? So overall, I'm looking at an m times n. So so far, I have m times n, but I have
7790.849 -> some additional work from this array, right? So notice that the n over here that comes
7796.679 -> from this for loop, I'm iterating through numbers, but then the additional M comes from
7800.889 -> copying over this array, which would be linear. So it would be m times n times M. And I can
7807.04 -> just kind of squish these two M's together. So it kind of boils down to m squared, times
7813.56 -> n, which is really the same thing as last time. And our space complexity would also
7817.03 -> be the same as it was last time, which was just m squared, mostly coming from the memo,
7823.58 -> right? And the logic is, your memo keys have m possibilities. But for each of those keys,
7830.4 -> their value can be an array of length m, right? So just m times m, or m squared. Awesome.
7836.67 -> So looking at this code, you probably, you know, feeling that Oh, my gosh, like this
7840.01 -> best some problem is pretty complex. And I think it you know, to be honest, it is, however,
7845.55 -> what I really want us to focus in on is this, like progression we took, right, I think that
7850.409 -> everyone could tackle this best some problem, if they warmed up and really understood simpler
7855.92 -> problems like can some and how some
7858.46 -> write the code is very, very similar. So to wrap things up, I will let's do some closing
7863.02 -> words on the drawing board. So in its best some problem, right, we had two inputs, we
7867.1 -> had m as our targets, and also say that n is the length of the array. So the brute force
7872.09 -> that we initially implemented it with just some recursion was exponential in time. And
7876.639 -> after we optimized it, we brought it down to just a polynomial time complexity. And
7881.46 -> notice that between our brute force and our memulai solution, they actually have the same
7885.849 -> space complexity. So we definitely prefer this memoized version. So hope you enjoy this
7891.429 -> series of problems. That is we worked on the can some how some and best some problems,
7896.17 -> they all had the common frame of us having some target some that we need to generate
7900.15 -> with some options given in an array. And in particular, if you look at the can some problem
7904.53 -> it asks us what that task of generating target some Can you do it? So yes or no? The house
7909.63 -> and problem. So is how will you do it? So what are the exact combination of elements
7913.539 -> that you'll use? And finally, the best son was the hardest version? And it asked, What
7917.82 -> is the best way to do it in terms of the least number of elements of the array? So there
7922.24 -> definitely is a logical progression to these problems. And in particular, they kind of
7926.17 -> capture a different variation of a dynamic programming problem for our can some problem,
7931.09 -> right, we had to return Boolean there. That's a type of decision problem, right? Yes or
7935.25 -> no? Can you accomplish this task? Is it possible? Along with that the house some problem was
7939.56 -> a combinatoric problem, right? We want to know the exact combination that works out.
7943.25 -> And the best some problem was a variation of an optimization problem, right? I want
7947.401 -> the shortest way to generate the target. So we saw that these three problem types definitely
7951.639 -> have some common ground, but they also have some nuance, depending on exactly the question
7956.07 -> we're trying to answer. These all fall under the umbrella of dynamic programming. That
7960.9 -> being said, dynamic programming problems aren't just limited to number inputs. All right,
7964.88 -> I think it's time to work on another prompt, let's say I gave you this, what I want to
7969.969 -> do is write a function called can construct that takes in some target string, as well
7973.989 -> as an array of words in a word bank. My goal is to return a Boolean indicating whether
7978.78 -> or not I can make the target by concatenate together elements of the word bank. And along
7983.84 -> with that, we can reuse as many elements as the word bank as we see fit. Alright, notice
7988.761 -> that in this problem, we're looking for a Boolean response to start, right. So just
7991.9 -> yes or no. Is it possible to generate the target? So let's take a look at an example
7996.519 -> here. Let's say I gave you this. So my target is abcdef. And I have a nice long array of
8002.739 -> some words. And so I'm basically asking, Can you construct abcdef, using elements of the
8009.079 -> array. So if I kind of take a look at the array, there is exactly just one way to generate
8014.69 -> the target string, which are just B, A, B, C plus D, F. So the answer here is true, because
8020 -> there is at least one way to make the target, right. Let's take a look at the opposite example.
8026.3 -> So let's say I had this word of skateboard. And I gave you all of these words in an array.
8030.91 -> So take a moment to kind of look this one over. And you tell me write Is it possible
8034.94 -> to generate skateboard here?
8038.019 -> And the answer here is no. Right? It is not possible to generate skateboard using this
8043.39 -> array of words. So we should return false, we can get pretty close to making skateboard.
8047.38 -> But we can never build the full string. Alright, so here are a few ways that we can attempt
8051.73 -> but they don't work out in the long run, right? This is one series we can take, but we kind
8055.94 -> of get stuck, as well as this. And also this way, right? Point being there are zero ways
8061.64 -> we can ever generate skateboard. So we should return false over here. Awesome. So I think
8067.07 -> let's look at one more example. We should already have the vibe that in general, it's
8070.86 -> easier to create a shorter string than a longer string, right? To create a longer string,
8076 -> you're probably going to need to use more elements. So if I have that kind of framing
8080.44 -> in mind, that I know that possibly the easiest string to create would be the empty string,
8085.829 -> right? So let's say your target was the empty string and I gave you you know, some kind
8089.65 -> of random array of words, really, the array of words doesn't really matter here. I think
8093.869 -> no matter what they should return true. Because to generate the empty string, you can just
8098.29 -> take no zero elements. From the array, and that kind of line of thinking is going to
8103.08 -> help us really start to solve this problem. So we'll kind of take this example, in stride,
8108.079 -> what we want to do is return true if our target is empty, and that takes place no matter what
8113.92 -> array of words we're given, right? Cool, more or less, that kind of sounds like a base case,
8118.73 -> right? But let's go ahead and start to kind of define some process we can take to explore
8124.03 -> all of the options, right? So I want to really visualize this in a well ordered way. And
8128.219 -> that really means a tree. Right? So let's say we're going to trace through this example
8132.679 -> visually. So my target is abcdef. We already said that, in the long run, we should return
8137.639 -> or conclude a true about this input, right? So we'll just start with the input argument
8143.139 -> that is the target string as the root of this tree. And this is sort of a scenario where
8148 -> like, Alright, I have two inputs, how do I know which one to really encode in my drawing?
8152.53 -> Well, it's about what will actually change. I know from like, one instance of like this
8157.91 -> problem to the next, the array of words, I can actually reuse as many times as I need.
8163.23 -> So it's not like I'm taking out elements from the array. So with that in mind, I think it's
8167.179 -> better or more reasonable to encode the actual target string into the nodes of this picture.
8171.869 -> Because I'm gonna start with this original target string of my route. Now I have to think
8176.219 -> about how I transition to the children of this right, so what moves do I take that hopefully
8181 -> shrink the target string, right, I know I need to shrink the target string, because
8185.789 -> I already have the base case of the empty string in mind. So I need to get closer and
8189.54 -> closer to a length of zero. We'll talk about one transition we can make. So I know that
8195.34 -> I need to sort of use the array of words as I transition. And so let's say I took a B
8201.35 -> as a choice right now, if I take a B, then I guess I could remove, you know, that sequence
8206.96 -> of characters from my parent node. So if I take a B out of abcdef, then the resulting
8213.47 -> child is just C, D, F. Cool. So notice that as we transition from one node to the next,
8219.45 -> looks like we're taking out that substring. In a similar way, I have another substring,
8224.07 -> that I can take out for my word bank, and that would be ABC, and that would yield in
8228.71 -> the child d f. And then I can do this for some other string inside of the word bank.
8234.2 -> And the really important thing to know is, there's a correct logic to doing this. And
8238.47 -> it's also a common trap I see a lot of students fall into when it comes to this logic. So
8243.25 -> right now I'll talk about like the common mistake. So it would be ill advised to sort
8248.42 -> of take out the CD from our root node, if you took out C, D, then the resulting note
8254.001 -> would be a, b, f. And so what we're doing right now is if we took out C, D, you're taking
8259 -> out something in the middle. And if you take out something in the middle, that means that
8263.54 -> your resulting string actually creates a new adjacent sequence of characters. In other
8268.03 -> words, I know that this is kind of suspicious, because look at this little run of characters,
8273.07 -> A, B, E, those are now like adjacent next to each other. Whereas in the original string
8278.15 -> of abcdef, A, B, he was not present, right. So if I kind of take out strings in the middle,
8284.95 -> then I'll have this sort of mistake of creating new adjacencies. Right. And that would actually
8290.72 -> impact the moves I take later on. So I don't want to do that, right. So the move here is
8295.1 -> to not take out any characters from the middle of the string. So what would be the correct
8300.9 -> way of doing it? Well, if you look at our two nodes that we already branched into, a
8306.2 -> common factor for them is the fact that we actually took a prefix out of our original
8311.22 -> root node, that is a B as a prefix, and so is ABC or recall that a prefix is just a string,
8318.09 -> that kind of begins some other larger string. So if I want to transition to a third node
8323.51 -> over here, there's only one more prefix I can take. And that would just be ABCD. Right?
8329.22 -> If I take that prefix out, then my resulting child is just E, F. And so the overall logic
8334.44 -> that we want to use when we build this tree is to only branch to children if we have a
8338.791 -> matching prefix in the word bank. And of course, the child would be the resulting string after
8345.26 -> we remove that prefix. So let's keep it rolling. And we'll apply this logic again and again,
8350.82 -> recursively. So let's say I'm rooted at this CDF node, I'll look inside of the word bank
8355.27 -> and notice any prefixes that actually match here, I think there's only one and that would
8359.18 -> just be CD. If I took out the prefix CD, then my result is E, F. In a similar way, I can
8365.43 -> do that for d f, I find that d f is actually contained in the word bang. So it's technically
8371.12 -> also a prefix. If I took the prefix def out of that node, then my result would be empty,
8377.13 -> which is pretty good. At this point, I look at some other nodes like this EF and they
8383.08 -> have no matching prefixes inside of the word bank. So they sort of bought them out in some
8388.33 -> sort of base case. In the long run. If I can break down EF any further than I know that
8393.9 -> it's not possible to construct EF so that can technically return a false to the parent.
8399.411 -> Mr way, if I ever run into the empty string, that means the job is done. And I can just
8403.67 -> return true, right? Something we said earlier was, whenever we want to generate the empty
8408.5 -> string, it is always possible no matter what. And now this pattern should feel a little
8413.261 -> familiar, right? All I have to do now is kind of bubble up these Boolean values to the parent.
8418.19 -> And overall, if one of my children return true, then I myself will return true, right?
8423.34 -> So I'll bubble this up a little bit, bubble up all the way to the top. So our root note
8427.63 -> chooses basically the true value among these three values it gets from each of its branches.
8432.33 -> And so the ultimate answer here is just true, right. And it is possible to generate abcdef
8438.71 -> using words of this array. So you're probably having deja vu right now, right? We almost
8442.721 -> use the same exact logic for the previous some problem. However, all we did was adjust
8447.95 -> how we transition from one note to the next, really making a compatible for this string
8452.32 -> data. But we're going to carry over a lot of knowledge, right? It's really important
8456.04 -> that we understand like the general like knowledge of dynamic programming and recursive understanding,
8461.351 -> we can apply that under any circumstances. That being said, let's look at one more example.
8465.82 -> What I want to do is see an example where this should return false. So let's say I gave
8469.99 -> you that skateboard example from before. In the long run, this is going to return false.
8473.51 -> If I tried to break down skateboard, I would always try to transition using any matching
8479.41 -> prefixes from the array. There are two prefixes at the start that match, and that would be
8483.59 -> SCA and SK, and those would give me t board and eight board, respectively. And now at
8489.561 -> this point, if I route myself at the keyboard node, I can only take the T, resulting eboard.
8495.761 -> In a similar way, if I am at the eight board node, then I can only transition using the
8500.991 -> eight. And I'm left over with board. At this point, if I look at eboard, that node over
8507.44 -> there actually hits a dead end, right, there's nothing I can actually take out of Eve or
8511.87 -> there's no matching prefix, because I basically need something that starts with E, but no
8516.19 -> words of the word bank start with E. So I guess we have to focus our effort elsewhere.
8520.62 -> Looking at this board, I can take either a Bo or a Bo AR, and I'm left with art and D
8527 -> respectively. And unfortunately, those two nodes at the leaf level also bottom out, right,
8532.79 -> I can't transition further and take out any prefixes from those strings. So I know that
8538.12 -> those will have to return false. And if all of these leaf nodes return false, then my
8543.38 -> ultimate note at the top that is a skateboard node at the root, that's going to just return
8547.61 -> a false as well. So it's pretty clear to me that the overall logic we want is if we get
8552.88 -> back a single true, then we can just return true all the way up to the call stack. But
8557.17 -> if everything is false, then we'll just go ahead and return a false. With that, I think
8561.75 -> we're ready to code this one out.
8563.41 -> All right, let's
8564.9 -> go ahead and code this one up. So here I have some initial examples we can use to test our
8569.551 -> code for correctness. Looking at the last example, over here, it's a fairly long one.
8573.851 -> And notice that the target incident F, whereas no words of the word bank have an F in it.
8579.16 -> So we know that that should result in a false. So let's go ahead and lay down the base case
8583.991 -> for this. Like we said, a reasonable base case is to check if your target is empty,
8589.24 -> right. If you have the empty string, then you can already construct the empty string
8593.8 -> by taking no words from the word bank. So you can just return true over here. And now
8599.08 -> I need to make my recursive call in a way where my target string gets progressively
8603.471 -> smaller and smaller toward this empty string. And so I know that based on the tree I drew,
8609.36 -> I need to make a choice based on the words in the word bang. So I'm going to iterate
8613.83 -> through all of the words. So I'll say for let word of word bank. So I'm iterating through
8619.641 -> every element of the word bank. And now that I have the word element, as I think about
8625.82 -> when it's okay, to make the recursive call using that word, and we spoke about this,
8631.701 -> we pointed out that we need to make sure that the word is a prefix of the target. So I can
8636.69 -> do that, I can just check if the target dot index of word equals zero. So index elf will
8645.391 -> just give me the index where I can find some substring inside of a larger string. If the
8651.13 -> index i get back is zero, that means that the word starts at index zero of the target.
8655.83 -> So if you're unfamiliar with this method in JavaScript really quick, let's say I did potato
8662.271 -> dot index of pot, that will tell me the index where I can find it, which happens to be at
8668.16 -> index zero. But if I looked for tayto, I would get the index of the T, the first t rather
8674.091 -> in potato. So this is a really nice way I can use to check if some substring is a prefix
8680.33 -> of another string, right, the index should be zero within it. So a way interpret this
8684.59 -> if statement is if I have a prefix, then I can sort of use it to shrink the target. So
8691.33 -> I'll create another variable here. I'll call it like the suffix so that's like the string
8694.891 -> after I remove the prefix. What I can do is slice my target string. Except to target that
8701.65 -> slice. And what I want to do is start picking up characters. After the length of the words
8707.54 -> I can say word dot length over here. So it's kind of reasoned out what this logic is doing.
8715.99 -> So we'll trace through this, let's say that I don't know my word. So I'll open up the
8720.74 -> note repple. Let's say my word was the string pot. And we'll also say that my current target
8727.62 -> is potato. So I know when I do target dot index of word, that is going to get back zero.
8735.2 -> So this if statement would be true. And then what I do is target dot slice of word, dot
8742.96 -> length. So we're dot length is just the length of the prefix I took, right? So it would be
8747.24 -> three over here, providing the target that slice starting at index three, that would
8753.13 -> give me everything after the prefix, right? So I basically have removed pots, and got
8757.961 -> eight. Oh, cool. So when you use slice, if you pass in a single argument, that's going
8762.931 -> to be the starting position of what you start grabbing characters, and you'll go all the
8767.421 -> way through the end. Cool. And that's the logic I definitely want here. So back to the
8772.521 -> code. Now that I have the suffix, I want to make my recursive call on that suffix. So
8777.58 -> basically asking, Hey, is it possible? Can I construct the suffix now? And I'll pass
8782.92 -> along the same choice of word bank. Nice. So here's where I should think recursively.
8789.101 -> So I'm focused in on what type of data do I get back from can construct I know I get
8793.65 -> back a Boolean, right? True or false, it tells me whether or not the suffix can be made.
8798.171 -> And I want to check, you know, if this call returns true, maybe I'll be explicit here.
8803.721 -> So if the recursive call is true, then I know that the original target can also be made.
8810.53 -> So what I'll do is return true early here. Cool, I know that if the suffix could be made,
8818.67 -> and the word that I use to generate the suffix is also in the word bank, then the entire
8823 -> target must also be able to be made. Nice to hear I have my nice early return true.
8829.26 -> Like you expect, where should I return false, it should be after the for loop, right? Only
8833.102 -> after I've tried every possible choice of the word and none of them worked out, then
8839.37 -> can I say, No, the target cannot be created. So I want to do a late return false over here.
8845.42 -> So this code is looking pretty sharp, I think let's go ahead and test these examples. So
8853.2 -> I should get true false true, and then false for the last one, give this a go.
8860.801 -> So I get true, false true. And it looks like the last example over here is taking quite
8866.131 -> some time to finish. So I think our code has correctness. But it's not efficient enough
8871.641 -> to you know, reasonably run this last example. So I'm going to kill this program. If you
8876.721 -> take a look at the example I pass it for this last one, it does sort of describe the worst
8881.16 -> case here. For one, we know that the answer is going to be false. So you will have to
8884.96 -> do a full exploration of the tree, meaning you can't do any early return trues, right.
8891.051 -> And because of like the length of the string, and also the length of the word bank, I have
8896.931 -> a very big tree, right? Notice that there's going to be a lot of prefixes at work here,
8902.431 -> right? every element of this array is actually a prefix that could be found over here. And
8907.581 -> that happens again and again. So I'll tell you what, let's go ahead to the drawing board.
8911.931 -> And we'll try to visualize what the exact complexity of this function is.
8917.58 -> Alright, so we implemented looks like the brute force for this solution. Let's talk
8922.21 -> about how we might want to optimize it, I think any conversation of optimization should
8926.53 -> begin with actually understanding what the current complexity is. So let's say I gave
8931.391 -> us this kind of large example to sort of visualize with, so my target string is enter a potent
8936.141 -> pot, and I gave you a pretty diverse array of words in the word bank. So I'll just go
8940.91 -> ahead and kind of give us though the full tree in what it would look like, would be
8944.4 -> this pretty long tree, right, so I follow the same logic that we did in our earlier
8948.9 -> examples. notice a few things. There are some occasions where I transition using a single
8954.55 -> character prefix, which is totally fine. And that kind of tends to feel like the worst
8959.32 -> case scenario, right? If you take single characters out of your target string over time, then
8963.95 -> you're going to have a very, very tall tree height, or you're gonna have to take many,
8968.2 -> many steps. So that kind of reminds us of a worst case scenario, like we saw when we
8972.811 -> ran the code. But that being said, we want to generalize this understanding right for
8978.08 -> basically some generic size of our input. So let's say we kind of just looked at this
8982.74 -> tree in terms of its overall shape, and then we'll kind of generalize it. So to me, this
8987.26 -> tree has this sort of basic structure. And let me start by defining the terms I'll use
8992.811 -> to describe the complexity right. So I'll say that M is the target string length, and
8997.63 -> n is the number of words in the word bang. Right, so I'm really trying to use a different
9002.761 -> variable for each of my inputs, because I have the vibe that both of them contribute
9006.96 -> to my complexity, possibly in different ways. So you've seen, you know, us analyze trees
9012.431 -> before in terms of like their structure, right, we're visualizing as a call tree, I know that
9016.33 -> the time complexity would be the number of nodes in the call tree, right. And so let's
9021.25 -> start with some familiar territory, I kind of already know that I need to realize the
9024.811 -> height of this tree, the height of this tree is going to be m, right? Remember that m is
9031.79 -> our target string. And so imagine, in the worst case that we took, or we had to take
9037.79 -> a bunch of single character, choice of words to construct our target string, that means
9042.96 -> that the number of things I would have to take all the way from the root to a base case,
9047.4 -> or the root to the leaf would be exactly M. Right? If I had to take a bunch of single
9051.891 -> characters, so we can be confident that the height of this tree would be m. So I talked
9055.91 -> about the height. Now I need to realize the branching factor, that is from one level of
9060.7 -> the tree to the next, how does a number of nodes change in the worst case? Well, I know
9065.671 -> that the branching factor is dictated by how many words I have in the word bank. And so
9070.61 -> that would be some relation on n. So I know at the root level, I have about one node.
9075.181 -> But then in the worst case, to get to the next level, I will multiply by n, right? Imagine
9080.51 -> that, basically, almost every element of the word bank was a matching prefix, right? So
9085.46 -> you'd multiply by n. And let's say that carried over further, let's say almost every word
9090.04 -> of the word bank was still a prefix of those resulting nodes, I would keep multiplying
9094.07 -> by n. And I would do this overall m times, right. So we already know that this is going
9100.29 -> to be exponential, I need to multiply n by itself, m times over. And so this would give
9105.99 -> me an N to the M, time complexity. In general, if you visualize your recursion using a nice
9111.771 -> call tree like this, then the overall time complexity is going to be the branching factor
9118.221 -> to the height power. So we have a branching factor of n and a height of m. That being
9124.42 -> said, this will just really consider the number of calls that we make, I want to be super
9129.15 -> sure and make sure we didn't do any other kind of performance or costly operations inside
9134.53 -> of our code. So here's our code, same code as we did before, if you look at this code,
9141.11 -> some things I need to consider are probably line six, right? If I look at line six, that
9146.45 -> was where I did a target that slice that's like copying over a part of the target string.
9152.95 -> And to do that operation, I would actually have to iterate through the target string.
9156.62 -> Right? So that would actually contribute to my complexity here. So in the worst case,
9161.69 -> would I be slicing? Well, if I'm slicing a target string, that would have like a maximum
9165.58 -> length of M, right. And so in every call to can construct, I'm going to have to do an
9170.5 -> additional m operation, if I have n to the M calls, then I can just multiply an additional
9175.771 -> M over here, right. So I, I added some additional term into our complexity, just multiplying
9180.5 -> by M. Cool. And so overall, if you look at that time complexity, it was already exponential
9185.581 -> so that multiplication by m, doesn't really make it that much slower, it's really the
9190 -> N to the M, that makes it unbearably slow in the first place. So that was the time complexity.
9196.51 -> Now let's look at the space complexity. So if we just refer to the space complexity,
9200.41 -> due to the call stack, it looks like it's going to be the height of this tree, right?
9204.7 -> Like we always say, the height of the tree would mean the maximum number of stack frames,
9209.44 -> we would need on the call stack before we bought them out at the base case, right? Because
9213.471 -> when we return from a call, we would actually remove something from the call stack, right?
9218.21 -> The height of this tree is definitely just M. So we'll say the space from the call stack
9222.311 -> is just o of m. But again, we should also you know, look at our code and see if we created
9227.2 -> any other like growing structures. So we look at the code right now, I see that on line
9232.9 -> six, and kind of talking about that slice statement. Again, the slice returns you a
9237.671 -> new string, right. And that new string is going to tend to be of length M. So on every
9244.181 -> call to can construct, I'm creating a new string, I actually need to maintain it through
9248.711 -> the recursion, right, because I actually slice before I return out on line eight. So because
9254.88 -> of that, I know that each of my M stack frames will have to store a string of length M. So
9261.761 -> that just means m times m in my space complexity, which is really just m squared. Cool, which
9267.971 -> isn't too bad of really a space complexity overall. Alright, so it looks like our final
9272.461 -> complexity for our brute force solution is exponential in time. And it looks like quadratic
9277.92 -> in space. So obviously, let's work on improving the time complexity over here. And so hopefully,
9283.461 -> you're seeing where this is heading. Let's say we jump back into my entropion pot example.
9288.54 -> So this was a huge tree. Take a moment look at this tree, and where can we actually optimize
9293.54 -> some work away?
9295 -> To we're trying to do is notice any overlapping some problems In the context of our tree,
9301.23 -> that means I'm looking for any duplicate sub trees. And I see duplicate sub trees right
9306.541 -> here, right? They follow the same structure all the way down even to their base cases.
9311.421 -> So I've looked at the sub trees, they're really trying to solve the same problem, right? That
9315.12 -> is they're both trying to figure out, can we construct the string and teapot, right?
9320.181 -> And if I saw that once, on the left hand side, the answer is going to be the same on the
9323.141 -> right hand side. So I can just store that information in a memo, right? So the trick
9327.53 -> is here to of course, memorize it using our classic strategy. So let's hop to it. All
9333.37 -> right, here we are back in my code editor. Let's go ahead and just memorize this one.
9338.61 -> So I'll create my memo object. And here, I'll prefer to use the target as the key to my
9345.601 -> memo, I know from one call to the next, the word bank doesn't change, right? So I don't
9351.45 -> need to actually make it a part of the key for my memo object. So I'll just check, hey,
9355.87 -> if my target is in the memo, then what I'll do is return the stored value of that target.
9364.09 -> Cool. While I'm here, let me also squish this down to a one liner. So I have the memo checking
9370.45 -> logic. Now I need to make sure I pass down that memo to all of my recursive calls. And
9376.32 -> then I need to actually store data in the memo. And the rule is wherever you have your
9380.46 -> recursive returns. Now, you should also store that return value in your memo before you
9386.08 -> actually complete the return. Right. So for both of these lines, I'll make sure I store
9390.71 -> them in the memo. So it's a memo at Target equals that. But I still want to return that
9397.15 -> same value. So we'll return true and return false over here. Again, notice that I'm using
9403.2 -> my exact argument targets, right? I don't need to write any memorization logic about
9408.95 -> the suffix, right? Imagine if we made the call to can construct suffix becomes that
9415.101 -> frames target. So the memos ation would still work properly. Right? So that looks pretty
9419.71 -> good. And it wasn't that much code, right? memoization is always this slightly extra
9423.59 -> layer, we add on top of our brute force solution. So let's try this now can't construct. Nice.
9430.62 -> And there we have that last example finishing fairly quickly, or we do get the correct answer
9434.811 -> of false over here. Alright, so here's what we'll do, we're going to head back to the
9440.73 -> drawing board. And of course, we'll come up with the final complexity analysis for this
9445.25 -> function. Some classic memorization, right? Let's take a lay of the land over here. So
9452.061 -> we wrapped up this can construct problem, let's describe its final optimized complexity.
9457.01 -> So again, like before, we'll say M is the target length, and n is the number of words
9461.11 -> in the word bank, our original brute force had an exponential time complexity, right,
9465.021 -> and that was n to the m times m. And when we memorize our solution, we actually improved
9469.221 -> it from exponential. So now, the time complexity of our solution is n times m squared, right.
9475.221 -> And the reasoning is, now we don't have to fully explore any duplicate subtree, every
9479.36 -> time we run into it, instead, we just store the results in the memo. And we can just kind
9483.79 -> of short circuit and fetch the stored result in the memo. Notice that I have an M squared,
9488.5 -> right in the memo, I have complexity, that second m really comes from the fact that we
9493.681 -> still have to do the slice, we have to pay for that cost. So although we added an additional
9497.84 -> object in our memo II solution, our space complexity would remain in the polynomial
9502.38 -> class. So overall, we definitely prefer this second solution, because we remove some exponential,
9507.771 -> you know, complexity. So now it is reasonable to actually run this in an amount of time.
9512.71 -> I think that wraps up this can construct problem. Now let's work on the counting version of
9517.811 -> this problem. In particular, I want to work on count construct. So we have the same set
9522.021 -> up here, I want to take in still a target string and also an array of words in a word
9525.37 -> bank. But this time I want to return a number, I want to return the total number of ways
9530.17 -> that the target can be made using words of the word bank. And like before, we can reuse
9534.841 -> elements of the word bank as many times as we want. So notice that this question asked
9539.38 -> us to do something slightly different, we don't want just true or false. If it's possible,
9543.15 -> we want the exact number of ways that we could construct the target string. So let's take
9548.843 -> a look at some examples. And also, you know, take a high level view of the strategy here.
9553.66 -> So let's say I gave you this string of abcdef. And this array of words, this particular example
9558.7 -> we saw before, and it is possible, and there's actually one exact way we can generate abcdef.
9563.49 -> So we can construct the tree in the same way we did before. And we end up with this. However,
9568.75 -> this time around, we want to choose a different value to return for our base cases, as well
9573.041 -> as changing the logic for reconstructing our sub solutions, right? And so if I look at
9578.08 -> the base cases here,
9579.62 -> I wanted to kind of just adapt the return value for this new type of data that I need
9583.381 -> to return which is number. So I think the move is for these scenarios where we can't
9588.891 -> break down our current target any further. That's your return zero, right? Yeah, f can't
9594.2 -> be broken down because it doesn't have any more matching prefixes from the array. So
9598.371 -> that means there are zero ways to breakdown F. And so we should return zero in those scenarios.
9603.94 -> And then when we have the empty string we know was successful. So we could always, you
9608.141 -> know, create the empty string. And so that should return one. And we've seen this logic
9612.79 -> before in problems like Fibonacci, as well as our grid traveller problem, we just want
9616.75 -> to bubble up these values to their parents, and make sure every parent will add up all
9621.17 -> the numbers that come back from their children. So it just bubbles up like this. And top level
9625.681 -> we do zero plus one plus zero, which means that we can generate abcdef in exactly one
9631.54 -> way. And if you look at the way that actually is symbolized inside the tree, it's exactly
9637.05 -> the path from this root node all the way down to that lone base case that returned an empty
9642.291 -> string, right, ABC plus d f. Let's take a look at another example. So here I have the
9649.251 -> target string purple, and I have some other characters and words inside of the array.
9654.16 -> So take a moment to look at the input here and predict what number they should return.
9661.25 -> So the answer here is two, right, there are two ways to create purple. So if you construct
9669.04 -> the tree, we know we start with the initial unknown of purple. And then we have about
9673.021 -> three prefixes that match here. And those yield some children, we can follow the same
9677.681 -> pattern recursively down the tree. So the full tree really looks like this, notice that
9682.931 -> I have a two base cases that actually end up as the empty string. So I know those are
9688.01 -> going to turn one up to their parent. And on the right hand side, I have a loan II that
9692.42 -> can't be broken down any further that returns zero. And like always, right, I just bubble
9696.151 -> up these values, until at the parent, I actually do one plus one plus zero, which gives me
9700.8 -> two. All right, there are two distinct ways to create purple using this choice of word
9705.37 -> bank. All right, I think I'm feeling pretty good about jumping into the code for this
9709.38 -> one, it's really just a small variation of the last problem we did. So let's hop right
9714.91 -> in. Alright, programmers back in the code editor, let's go ahead and bang this one out.
9718.601 -> So I already have some examples that show us what numbers we should return for this
9722.42 -> count construct, right. So I have some examples here. I'm gonna say even have that big example,
9727.12 -> with our large inputs, we know that that will probably need to be optimized, let's lay the
9731.03 -> foundation with a brute force. So we'll start with the same base case, as always, right.
9734.431 -> So if the target is the empty string, then we have truly solved the problem. Meaning
9739.54 -> that there is definitely one way to generate the empty string, right, just take no elements
9744.12 -> from the word bank. Along with that, I need to make my recursive call. So the usual structure,
9749.03 -> right, I want to iterate through every choice of word. So for let word of word bank. And
9754.86 -> then for every word of that word bank, I need to check if it's a prefix. So we've done this
9760.101 -> before, if the target dot index of word bank, or rather index of word, if that is equal
9768.91 -> to zero, then it must be a prefix. And if it's a prefix, and I can go ahead and take
9774.59 -> like the rest of the word that is the suffix and call recursively on it. So I'll do this
9779.54 -> in line now say, count construct, and I'm going to pass in the rest of the word, which
9785.29 -> would be target dot slice, and then I'll slice starting at word dot length. So this means
9791.771 -> that the slice contains everything after the word or everything after the prefix. And so
9799.17 -> Also be sure to pass into the second argument, the same word bank. Nice. And now I need to
9805.07 -> think about what calc construct returns. So it's returning a number now, right? So in
9810.561 -> particular, this will be the will say, num ways. I'll say no more ways for the rest,
9818.931 -> maybe a little wordy of a variable name, but I think it does describe how exactly what
9823.22 -> this returns here, right. So this is a number of ways that you can generate the suffix with
9827.681 -> like the rest of the target. What I want to do is keep a running total. So outside this
9832.921 -> for loop, I need a way to add everything that the for loop iterates through right, so I'll
9837.53 -> create a total count over here. So I'll say let, I'll say total count. And I'll start
9843.58 -> it as zero. And now inside of my for loop, whenever I get the number of ways to create
9849.96 -> the rest of the string, I'll go ahead and just add that into my total count. So it's
9854.17 -> as simple as total count plus equals the number of ways to generate the rest of the string
9860.291 -> right after I've removed the word. And after I take the total through the entire for loop,
9866.33 -> and I can just return the total count. Something really great about this, this programming
9871.87 -> pattern is let's say that none of the choice of words were a valid prefix. So that means
9878.101 -> I finished this for loop. And this if statement is never true. If this if statement is never
9882.51 -> true, that I never add anything into the total count. So if I return total count afterwards,
9887.79 -> I return a still zero, which makes sense because apparently there are zero ways to make the
9892.931 -> target right have no initial step to take in the word bank. So let's go ahead and test
9898.69 -> this one out. So looks like we should get to 104, and then also a zero at the end. Let's
9906.58 -> give that a go.
9909.341 -> So 2104. And it looks like the last example is just a little slow for us. So you already
9915.71 -> know where this one is heading. Let me tell the program, let's just go ahead and just
9919.94 -> memorize this one off the bat, right? Hopefully, I think through all of these examples, you're
9925.24 -> feeling really good about memorization. And it's apparent that we basically have drilled
9929.25 -> into our minds already. So if the target is in the memo, and just return the stored value
9935.58 -> memo at Target. And then I'll need to adjust my return value, right. So where I return
9941.75 -> over here is I'll store that total account as the value corresponds to the target. And
9948.03 -> let's not forget to also pass in the memo to our recursive calls, right? So memo goes
9953.131 -> right here. Nice. So let's give that a run again. I expect a zero for that last answer,
9960.84 -> right. So looks like I have an error over here. And of course, I forgot to actually
9965.2 -> replace my return value. So I can still return the total counts. And addition to that, I'm
9969.291 -> actually adding it into my memo. So let's try that now. Yeah, these answers look correct,
9975.04 -> right, I got zero for the last one, notice that the last one is not possible, because
9978.851 -> it ends in an F and all my words only contain E's. Nice. And if you look at this code, it's
9985.54 -> a pretty similar to our previous problem of can construct if I kind of split these can
9992.58 -> construct basically has the same shape. Really, the only difference is this number variable
9998.12 -> that I add into, and of course, the return values. So we do expect the complexities to
10002.96 -> be the same for these two functions. Let's wrap this one up on the drawing board. Let's
10008.17 -> look at the complexity of this. As always, for this calc construct problem, our M is
10012.15 -> going to be the length of a target string, and our n is going to be the length of the
10015.87 -> array, right? So that means a number of elements in that array. Our brute force is the same
10020.15 -> as it was in the can construct problem, right, so it's still exponential. And using memoization.
10025.37 -> We just brought that down from an exponential to a polynomial time complexity, and the space
10030.021 -> complexity remain the same. Really, there is no additional cost that we pay in our implementation
10035.63 -> for this counting version of the problem, right. The only difference is now we're maintaining
10039.44 -> a number that we just add to, you know, on every iteration of that for loop. But that
10043.591 -> doesn't really affect the runtime or the space complexity at all. So I think this wraps up
10048.2 -> this count construct problem.
10051.5 -> Now I want to do one more variation of the string problem. So what I want to do is write
10055.42 -> a function called all construct that has the same setup, right, so I'm going to take in
10058.831 -> a target string, as well as an array of words in a word bank. But this time, what I want
10062.91 -> to do is return all of the ways that the target can constructed by concatenating elements
10067.54 -> inside of that word bank. And that means I should return a two dimensional array, a single
10072.72 -> element of that 2d array is going to represent one of the combinations that can construct
10077.04 -> the target, right, and I want to return all of the ways within a 2d array. And as always,
10081.76 -> we can reuse elements of the word bank as many times as we need. So let's make sure
10085.83 -> we understand this question by looking at some examples and their output. So here's
10089.69 -> an example using purple. And it's the one we've seen before in the last problem. The
10093.57 -> last problem I had us return to because there were two ways to create purple. But now I
10097.28 -> don't want the number, I want the exact ways that actually create purple, that means you
10102.161 -> need to return a two dimensional array, the outer array, or the outer set of brackets
10106.521 -> represents the collection of all combinations. Whereas a set of inner brackets or a sub array
10111.2 -> represents one of the combinations that creates purple. Let's take a look at another example.
10116.4 -> Let's say I gave you abcdef. And I gave you this long array of word bank, notice that
10121.34 -> I actually added some elements inside of this word bank array. It's a little more complex
10125.351 -> of in our previous examples. Because of this, there are actually many ways you can create
10129.12 -> abcdef. And this contains all of them, right? So there are four ways to create our target
10134.13 -> string. And again, notice that each sub array represents one of those combinations of words
10139.271 -> in the word bank that creates the target. Cool. So now that we kind of understand like
10144.8 -> the shape of this problem in terms of the data we should return, let's take a look at
10148.71 -> some base scenarios. So let's say I gave you this example. Right? So I have a target of
10154.811 -> Hello. And my words in the word bank are just Cat Dog and mouse. Obviously, you know, that
10159.431 -> is not possible to generate Hello. So there are really zero ways to create this. How should
10164.79 -> we actually return an answer here? Well, I think it'd be reasonable to return an empty
10169.3 -> array. Remember that we're saying the outer array in this context represents the collection
10174.181 -> of all of the combinations that can create Hello, since there are zero ways to create
10178.62 -> Hello, that means that our collection is empty, right? This has a length of zero meaning there
10183.23 -> are zero ways to create Hello. Now let's say we had another base scenario. Let's say we
10188.311 -> have to generate the empty string using the same array of word bank. And that scenario,
10192.79 -> I think it's reasonable to return an array containing an empty array. And again, the
10198.25 -> reason is if it is possible to To create the target string, then we need to return a 2d
10202.721 -> array, right where the outer array represents the collection. And if there's one sub array
10207.051 -> inside of that outer array, that means there's one way to create the empty string. And what
10210.601 -> does that one way? Well, it's to take no elements from the word bang. So I think this is going
10215.61 -> to be consistent logic, this is going to be a really important way to think about this
10219.4 -> problem. That is, when we're given a target that cannot be constructed using the word
10222.96 -> bank, we should return an empty array, right, a one dimensional empty array, because there
10227.08 -> are zero ways to create it. On the flip side, if I've ever given the empty string, we know
10231.891 -> that it's always possible. And so we should return like a two dimensional empty array.
10236.54 -> Cool. So with that, let's look at some tree examples. Now, let's say I have this large
10240.95 -> example from before, we know that the overall tree like we've always know created would
10245.21 -> look something like this. Obviously, now we're just reframing this problem in the return
10250.971 -> values that we do for our base cases. So you notice that there are four base cases here
10255.94 -> that kind of reach the empty string. And I know that those represent the four ways to
10260.141 -> create abcdef. And now I have to adjust the return values here. So I'm already saying
10265.45 -> that if I have the empty string, then that should return an empty 2d array. So it looks
10270.83 -> something like this, right? And how I actually reconstruct the entire solution from these
10277.19 -> very small sub solutions. So let's just stay focused on the left subtree. Right now, I
10282.061 -> know that these arrays are going to return back to their parents. And when I do that,
10286.67 -> I also need to make sure that I include the edge that I actually use to transition to
10291.54 -> the child. In other words, I'm going to be sure to push the label for the edge into each
10296.5 -> of those sub arrays. So the key insight is this, right? I'm just pushing those edges
10301.07 -> into their sub arrays. And from here, I continue the pattern up a little further, right. So
10305.23 -> I know that these arrays return their parent over here, and I still, you know, push the
10311.32 -> edges that I took to get to those children. So on the left, I'm going to put CD in the
10315.631 -> front, and on the right, I'm going to put C in the front, resulting in this. And then
10320.211 -> at this point, notice that this node rooted at C D E F, that is actually going to receive
10325.641 -> both of these collections, right? And what should this note do? Well, it really needs
10331.811 -> to just combine a both of these arrays. Recall that, you know, we technically receive a two
10337.48 -> dimensional arrays, which means that a sub array represents one of the ways to create
10343.42 -> the actual target. So if I just concatenate these two, two dimensional arrays, that yields
10349.97 -> this construct, if I do a quick spot check, I'll see if this is compatible with what the
10354.28 -> question is asking, right? This is a two dimensional array. And I know there are actually two ways
10359.33 -> that we can create CDF, if I look at what this is saying, The first way is to do C,
10363.851 -> D plus F. And the other ways to do c plus d f. And so this cell problem is correct in
10369.99 -> itself. So I'm going to use the same exact logic to trace through the right hand side,
10374.05 -> I know that these cases return to their parent. And the parent is responsible for adding the
10379.26 -> edge to each of those sub arrays like this. Now, I'm just gonna reorganize everything
10384.07 -> at the top just to give us some more room. And maybe I'll even spread out these brackets
10387.811 -> to make a look more symmetric. But I'm not done here, I still need to consume each of
10392.24 -> the edges at the very top of the tree. So if I look at my left hand set of arrays, I
10398.301 -> know that those need to receive AB. In other words, they need to have a B place at the
10402.811 -> front of each of those. So I'll just do that, right. In the same way, all of the arrays
10407.931 -> in the middle, which is really just a one sub array, need to add ABC to the front of
10412.2 -> them, like this. And finally, same thing on the right hand side with ABCD. Cool. Now if
10418.171 -> I look at what I have, these represent the four ways that we can actually create our
10423.04 -> original target string. But at this point, the root node just needs to concatenate each
10427.91 -> of these two dimensional arrays together. So it really just combines them into a single
10433.25 -> 2d array like this. And if I do a quick sanity check, I know that each of these sub arrays
10440.641 -> represents one of the paths that I can take to a base case down my tree, which is exactly
10445.49 -> what we intended at the start. So this type of recursion is definitely more complex than
10450.301 -> the previous examples. But hopefully, you can recognize what's similar to our last examples,
10454.12 -> right? Let's do one more together, though, let's go back to this purple example of the
10458.9 -> full tree would look like this. And we'll trace through this. So on the left hand side,
10463.37 -> we're going to start with a 2d empty array, right, because that's the base case for the
10467.79 -> empty string. Meaning that hey, it is possible to create the empty string in one way, and
10472.851 -> that is to just take nothing. So I do the same logic as our last example, meaning I
10477.2 -> returned to my parent, but I'm being sure to also include the value of the edge, right.
10482.33 -> And so the key insight here is, I'm being sure to add the edge label and not the actual
10487.851 -> node label here. The note and the edge happened to have the same thing le, but I'm really
10492.62 -> looking forward to adding the edge right? So I add Ellie over here. And likewise, this
10498.01 -> returns to its parent even further, and so Add that last edge of perp. Cool. Now I need
10503.84 -> to do everything for this middle path. And so what I'll do is return the empty 2d array
10508.891 -> at the very bottom. And this really bubbles up all the way to the top. And we just accumulate
10513.26 -> every edge label as we go, right. So it sort of looks like this.
10520.9 -> And what's really interesting about this a right hand path, this is actually a base case,
10526.62 -> that doesn't work out, meaning we kind of hit a dead end at EA. And what we said in
10531.841 -> our initial examples was, whenever we have a target string, that cannot be created at
10537.3 -> all using words of the word bank, then we're going to return a one dimensional empty array.
10542.69 -> And that actually works out in our favor, because if we return this empty array to our
10546.431 -> parents, our parent is going to try to add the edge into every element of this empty
10552.851 -> array. But if there are no elements in this array, and it's going to add nothing. In other
10556.891 -> words, this one dimensional empty array really just bubbles up to the very top. And like
10562.69 -> always, if I just concatenate all three of these arrays together, I actually end up with
10567.931 -> my final answer, notice that when I concatenate an empty array to the rest of these, I will
10573.061 -> actually contribute nothing to my file answer, which makes sense because there are no paths
10577.75 -> that work out on that right hand side. So our final answer is just this. Like, we know
10582.75 -> both of those sub arrays represent the two different ways that we can create purple.
10586.15 -> All right, I'm feeling pretty good about this process. Now, let's go ahead and code it up.
10590.5 -> Alright, programmers back in the code editor, another problem, another solution. So let's
10595.181 -> start by laying out the base case over here. Now, like we said, we'll say if the target
10600.15 -> is the empty string, then we want to return a two dimensional empty array, right? Every
10607.05 -> single sub array here represents the one way you can create the empty string by taking
10611.021 -> nothing right I take no words of the word bank. Nice. And then besides that, we need
10615.651 -> to make our recursive logic. So that's going to be very similar to what we've always done
10619.65 -> for this style of problem. So I'm going to iterate through every word of the word bank.
10623.641 -> So I'll say let word of word bank word bank. And I need to still check if there's a prefix,
10630.86 -> right? If this word is a prefix, so if target dot index of word, if that index is zero,
10638.28 -> then I know that it must be a prefix, so I can continue some code inside. Nice. And so
10644.08 -> what I'll do is I'll go ahead and create an extra variable just to store the target after
10649.33 -> I remove the word which would give me like the suffix so I'll say const suffix equals,
10654.65 -> and I'll say target dot slice of word dot length. So you've seen this pattern before.
10660.7 -> But just to refresh, this just gives us everything after the word. So basically, once you move
10666.07 -> the word, what is the remainder of the string all the way to the end. And now that I have
10670.221 -> this suffix actually wants to call my function recursively on that remainder, right? Just
10676.11 -> like this, I'll pass along the same word bank. Cool. And now here's where things get a little
10683.131 -> intense, right? So we've been doing a lot of problems, you know, using this recursive
10687.74 -> structure, and then we memorize it in the long run, I think the most important thing
10690.99 -> to do is when you make your recursive call, you really just assume that your function
10695.16 -> works, right. So I think about what type I should get back from all construct. So I know
10700.051 -> that all constructs and give me back an array, right, it's gonna give me back an array containing
10704.41 -> all of the ways to make the suffix. If there are no ways to make the suffix, then it's
10708.62 -> going to give me like an empty array, right. And so I'm going to assume that here, so I'll
10713.86 -> create a variable. And I'll call it, let's say, the suffix waise. And the reason I'm
10718.73 -> naming it like this is, I really want us to, in our head, think about the return value
10723.341 -> from this as an array of all the ways to build the suffix. Cool. So that's going to be a
10729.74 -> two dimensional array, right? There are many ways to do it. Nice. So if this gives me all
10735.581 -> the ways to make the suffix, how can I get all the ways to make the original target like
10741.71 -> in this current stack frame? Well, what I need to do is really just take each of those
10746.811 -> suffix ways, and add my word to the front of it, right? I used this word to even create
10754.38 -> the suffix in the first place. So what I can do is, I'm going to say my target ways. Right?
10761.44 -> So now I want to relate how the suffix ways can be used to build my original target, right?
10766.78 -> All I need to do is really iterate over every sub array over here and add my word to the
10774.12 -> front of it. Right, that's exactly the process we took in the tree diagram, right? So I can
10779.08 -> use some nice JavaScript methods here. If I just wanted to basically manipulate every
10784.22 -> element of the array, I can do a map here. So I'll say suffix, ways dot map. And I know
10790.96 -> that a single element there's going to be one way he'll say, singular. And then what
10795.55 -> I want to do is just take that same way, copy it over, meaning I just spread It's elements.
10800.69 -> But I'm also going to put the word that I took in the front, we've seen this syntax
10807.36 -> before, although maybe this like map method is maybe new free, maybe you're not super
10812.021 -> familiar with JavaScript. I think that's fair game. So let's hop into the node repple. Just
10816.45 -> to demo this, let's say I had some array, I'll just make this array 1234. So there's
10822.7 -> my array. What I can do in general is use array dot map. And map is a function and you
10827.98 -> should pass in a callback. So that means another function. And what I'll do is this callbacks
10833.53 -> gonna take in every element, and it's gonna be an arrow function, what's going to return
10838.19 -> is really just how I want to modify the elements, let's say I wanted to multiply every element
10843.99 -> by two, right, the return value of this callback function is going to become a new element
10850.44 -> of the new array. So notice, I get back 2468, my original array was 1234. And I get back
10856.971 -> a new array using map. And so essentially, what I'm doing in this example, on line eight,
10862.33 -> in my actual code, I'm just taking every sub array and just inserting my word at the front
10867.94 -> of every sub array. So an example that speaks closer to our exact code would might look
10872.641 -> something like this, like suffix ways. And it's going to be a 2d array. So I'll make
10877.44 -> a sub array, and we just an array of some strings kind of arbitrary right now. So I'll
10880.851 -> say x, y, and then z. And then the second sub array will be maybe I don't know, like,
10887.88 -> a x and then a yz. Right, so that's my original array. And what I want to do is, if I say
10894.8 -> suffix ways dot map, and let's say for every way, that is every sub array, I just want
10902.111 -> to copy over that way by spreading out. And then I'll just go ahead and put I don't know,
10907.15 -> like an A at the front, just like this. So notice what I get back, I basically have every
10911.891 -> sub array from before, except now I have a as the first elements of both of those. Cool.
10917.34 -> So that's all I'm doing in this particular chunk of code. Awesome. So we'll leave the
10922.921 -> node repple. Back to our running solution. And now that I have the target ways, that's
10928.771 -> good. It's basically just like a piece of the answer I need. However, it now this is
10933.53 -> really only going to give me all of the ways to make a target that use this word that I'm
10938.96 -> currently like on in my iteration. However, I know that this for loop gives me multiple
10944.29 -> branches, right, it's going to use all of the words that are possible to create the
10948.021 -> target. So I need to kind of gather them all together. So I'll create a variable on the
10952.96 -> outside, I'll call it result mailstore. Everything right? So across all of these iterations,
10959.48 -> I just want to add the target ways into the results, I'm going to do result dot push target
10965.53 -> ways, and I need to actually spread this out that way, I don't nest things too deeply.
10971.051 -> Right? Remember that this is a one dimensional array, right now, target ways would be two
10976.271 -> dimensional, I don't want to just push target ways into result. Otherwise, I'd get like
10980.811 -> a three dimensional array. So I'm going to be sure to spread out target ways, right.
10985.801 -> So with that, let's actually go ahead and give this run I think all I need to do now
10990.37 -> is after my for loop, right, after I'm done getting all the ways, then I can just return
10995.83 -> the results of Mr. Just work recursively.
11000.771 -> So let's just try maybe the first example for now and see how we do. So I should get,
11005.761 -> you know, a two dimensional array where I have two ways to make purple. Right? let's
11008.681 -> give that a go all construct. Nice, and that's looking pretty good. That is a correct answer.
11016.01 -> Let's try these other ones. Now while we're here. So we should get a nice, large array
11020.59 -> for that second example. Nice, here it is. And then the third example, skateboard should
11026.24 -> return just an empty array, because it's not possible. And it looks like that last example
11029.75 -> is also not possible. So we do get an empty array. And if you kind of notice, to get that
11034.71 -> last answer, like we did in our other examples, it does noticeably take us some amounts of
11039.221 -> time. But before we get to that, let's take a look at this code, make sure we really understand
11043.951 -> it. So the biggest logical leap we made here was really, you know, assuming that we get
11048.891 -> back valid data from line nine, right? And then it's all about how we can, you know,
11054.99 -> adjust that sub result to get our full answer, right. So if I get all the ways to make the
11060.47 -> suffix, and for each of those ways, I can just add my word in front of them. And that
11065.472 -> would give me all the ways to make my target. Right. And I need to continue that process
11069.83 -> for every choice of word, which is why I'm maintaining this like a massive results variable
11075.431 -> outside of the for just pushing all the ways into this result over time. And maybe just
11080.641 -> to be super clear, you're not familiar with like this push method, especially when you
11084.561 -> use the spread operator with it as well and really easily step through that. So in isolation,
11090.391 -> right, this is just using push and spread in general, if I had some, let's say, array,
11096.29 -> and let's say it was just an array of just some elements 1234 Then I had another array,
11102.771 -> I'll call it just numbs, let's say in nums, I had elements of seven and eight. So here's
11110.98 -> my original array. And here's my nums. And if I did array dot push nums, you're actually
11118.9 -> going to force some nesting in this array. So array dot push returns the new length.
11124.891 -> But it also actually manipulates or mutates, the argument you call it on. So if I look
11128.751 -> at the array, now, notice that it actually has another array nested inside. That's because
11134.12 -> I literally just pushed the entire nums. If you wanted to do something a little different,
11138.28 -> what you can say is array dot push, and you spread out nums, what's going to do is comma
11144.87 -> separates the seven and eight, basically removing the outer brackets from it. So have a look
11149.181 -> at this. Now, look at the array. Notice that with that second push, now I just added a
11155 -> seven and eight without those additional brackets. So that's all I'm doing over here. Like I
11158.9 -> said, I don't want to add another level of nesting to the result array, right, it should
11162.8 -> just always remain at most a two dimensional array. Cool. But overall looks like we have
11170.2 -> solved this one. And let's kind of talk about if there is even a way to make this faster.
11175.891 -> This is kind of an interesting problem. So this is looking pretty good. So earlier, when
11181.521 -> we were on the drawing board, we said that, alright, because of what they're asking in
11185.41 -> this problem, it is basically going to require a full exploration, right? If I want you to
11191.05 -> return every possible way, to make the target string, you'll just have to kind of do all
11197.431 -> the work of creating all of those sub arrays, right, so we can't really avoid that stuff
11202.181 -> over here. If you think about this example, unlike 3334, this is actually not the worst
11208.891 -> worst case anymore. All right, here, we know that eventually we're going to return just
11213.92 -> an empty array, because we can never generate the final z in this example. However, if I
11220.37 -> remove the Z, then the result will be a very, very massive 2d array, because there are a
11225.431 -> bunch of ways to actually make this target string. Right. So we've kind of changed the
11231.12 -> direction of what it means to be like worst. On this example, let's say we brought this
11236.59 -> to use the Z. If you really cared about optimizing, like this particular scenario, you would find
11243.24 -> some benefit to like memorizing it, although it wouldn't really affect the worst case,
11247.15 -> because the worst case is actually where you return a two dimensional array and not just
11251.79 -> an empty array. So maybe just because it's good practice, well memorize this. But when
11257.511 -> we go to the drawing board, we'll see that this actually doesn't affect the true a bigger
11261.641 -> worst case. But if you just want to optimize it slightly, let's say we baked in our memo
11266.26 -> over here, then we check, you know, if our target is in the memo, and if it is, then
11272.07 -> we return the memo, add the target, we'll be sure to pass along that memo to our recursive
11277.851 -> calls. And then we just need to make sure that for our return value on line 16, we actually
11282.391 -> store that inside of our demo
11286.71 -> using the target as a key, and then we still complete the return by returning result like
11290.75 -> we once did. Cool. So let's run this now. And I would agree that the last example now
11298.811 -> runs faster. But it is not the worst case is a really important thing to understand
11303.69 -> here. Right? The worst case is when you actually have to create a massive sub array, right.
11309.65 -> And so let's head to the drawing board and see our final conclusion about the complexity
11314.61 -> here. Like we usually do, well define m to be the length of the target string, and n
11319.24 -> will be the number of words in the word bank. We know that this problem asks us to return
11323.44 -> an array containing every combination that generates the target string. And to come up
11329.07 -> with that answer, we have to visualize it like a tree sort of like this. In our previous
11333.34 -> drawing, we saw how each leaf of this tree represents one way we could create the target
11339.08 -> string. In other words, if we can figure out the total number of leaves in a tree like
11343.79 -> this, then we will basically have the number that represents how many different ways or
11348.11 -> how many sub arrays we have to generate in this problem. We already know that based on
11353.24 -> our process, we described that there are dimensions to this tree, we know that the height of this
11358.181 -> tree is going to be m and we know that from one level to the next, we really just multiply
11362.69 -> the number of nodes by n. And that means at the base of this tree, we would have entity
11367.03 -> m number of nodes, right, so we have n to the M leaves, which means that we have n to
11372.761 -> the M different combinations that generate the target string. And if we have to represent
11378.68 -> each of these entity m combinations, then we definitely need n to the M sub arrays in
11383.501 -> our output. And so what we're saying here is no matter what clever implementation we
11387.75 -> create, we really have to return a result that is exponential in size. And that's really
11393.36 -> going to drive the complexity of this one. You can't do any better than exponential over
11398.011 -> here. And its overall will say at the time. complexity of this is n to the M or just exponential.
11402.82 -> If you want to split hairs here, you can really multiplied by some additional factors of m.
11406.62 -> However, the exponential nature of this alone really gives us the overall complexity, right,
11412.641 -> because once something is exponential, there's really no coming back from that. And in a
11416.061 -> different vein, we can say that this space complexity for this is O of M, like we usually
11420.23 -> say, it's really just the height of the recursion tree. And an example like this, where output
11425.62 -> is very large, we usually don't include the size of the output of the size of the result
11431.521 -> into our space complexity, which case obviously, would also be exponential in space, because
11435.921 -> the result is exponential. Right. So here, we'll just refer to the additional space we
11441.63 -> use for the call stack, which would be o of M, right, any solution you come up with for
11446.021 -> this problem would be just about this fast. Alright, and there, we have our all construct
11451.511 -> problem.
11454.53 -> So at this point, we've gone over many dynamic programming problems, and we use that memoization
11458.37 -> strategy to work out a solution for all of them. However, memoization is only one of
11463.12 -> the ways we can actually implement a dynamic programming solution. Right now I want to
11467.41 -> revisit all of those problems, but this time use a different lens of understanding. As
11471.68 -> always, I want us to ease into things. So we'll warm up with this fib function, this
11475.12 -> is the same problem as last time. In other words, I want us to return the F number of
11478.83 -> the Fibonacci sequence. Here, I'll say that the zeroeth number of the sequence is zero.
11482.76 -> And the first number of the sequence is one, you might notice that this time around, I'm
11487.03 -> saying that the zeroeth number is zero and the first number is one. Whereas in the first
11490.641 -> time we did this, we said the first number is one and the second number is one However,
11494.25 -> no matter how you start the sequence, they will both actually give you the same series
11497.91 -> of numbers. In other words, down below here, I have some examples for n as well as what
11501.96 -> the output for our fib function should be. In other words, if I asked you for the six
11506.561 -> Fibonacci number, the answer there is eight, like we always expect, alright, let's get
11510.69 -> into the heart of this strategy, right? What does tabulation even mean? Well, it's all
11514.58 -> about building a table. So what we'll do now is step through a tabulated strategy for calculating
11519.28 -> fib of six. I know that in the long run, I ought to return the final answer of eight,
11523.91 -> right? So I hope that I get the answer right by the end of this little trace. So with tabulation
11528.891 -> we're choosing to do is really think about this dynamic programming problem, still in
11532.931 -> terms of subproblems. But instead of doing it recursively, we do it iteratively by building
11538.041 -> a table, really just an array, and I'm going to begin it with roughly the size of the input.
11542.851 -> In other words, if my input here is the number six, then I basically want an array of length
11548.67 -> six, notice that if I want the indices of this array, it's kind of line up perfectly
11552.9 -> with the original input number. And I'm going to have to actually add an extra position.
11557.17 -> In other words, this array spans indices zero through six, which kind of means that there
11561.83 -> actually are seven different elements here, right, the length of this array is seven.
11566.521 -> So something very common that I see when students try to implement a tabulated strategy is they
11570.9 -> kind of overlook this off by one nature, right? So technically, I'm creating an array that
11575.84 -> has one greater length than my number input. But any case, if this is my array, what do
11581.161 -> I want it to represent? Well, in the long run, I want to actually fill out this array
11586.92 -> in a way where each subproblem corresponds to an elements of this array. So here's how
11591.78 -> I'm going to begin this table. The move here is to actually initialize every position of
11596.021 -> this table with zero. And the reason for me initializing zero everywhere in this array
11600.28 -> is the fact that I know Fibonacci requires me to take a sum, right, and zero is a really
11605.18 -> great starting value when I need to calculate a running sum, right. But along with that,
11609.75 -> I need to be sure to seed the starting values within this table. In other words, if I look
11614.511 -> at the zero position of this table, it already has a zero, which makes sense because the
11618.87 -> zero number on the Fibonacci sequence is zero. But what I should also do is make index one
11624.9 -> contain a value of one. And that kind of entails that, hey, the first Fibonacci number is indeed
11630.95 -> one. And now at this point, once I've seeded those initial values, now I can actually run
11635.8 -> the general algorithm to fill out this table. So now it's just a matter of iterating through
11641.12 -> this table, so I'm going to start you know, at the very first position of the table, probably
11644.76 -> just with a regular for loop. And what I need to do is really remember the definition of
11649.16 -> Fibonacci, right. So if I have this number, currently in the array zero, that's a zero
11653.98 -> Fibonacci number, what I can do is just add this current number that I have kind of pointed
11659.07 -> to in yellow to the next two positions. And the reasoning there is a Fibonacci number
11664.332 -> is used to contribute to the sum of for the next two Fibonacci numbers. So I'm going to
11669.301 -> do is take the zero and add it to the next two positions, obviously, since it's zero,
11673.9 -> actually don't change the values of those next two positions. It's nothing really is
11677.57 -> calculated on that first iteration. But any case I can continue on to the next iteration,
11682.93 -> so my current position is always in yellow, and the next few positions are pointed to
11686.63 -> in blue. At this point, my current position says one is what I should do is take one and
11691.811 -> add it to both of my next positions. So that means two and three are indices two and three
11696.86 -> are contained both a one and a one which so far, make some sense because at least for
11701.811 -> the to the second number of the Fibonacci sequence is indeed one, I can do the same
11707.97 -> thing, right, my current position contains a one. So I'll add that one into my next two
11712.25 -> positions. Keep doing this, I add two to my next two positions, at three to my next two
11719.221 -> positions.
11721.63 -> And at this point, I sort of reached the end of my array. So I know that I probably shouldn't
11725.391 -> step out of bounds. So really just want to look one position ahead over here. And at
11729.51 -> this point, I just add five into my next position, which actually works out to an eight being
11734.9 -> stored in index six of this array. Which makes sense because logically, the six number of
11740.19 -> the Fibonacci sequence is a just like we intended. That's all there is to tabulating Fibonacci,
11746.42 -> the most apparent difference from our previous like recursive strategy was, this is not recursive,
11751.4 -> right? This really just requires us to iterate through an array. So to fully iterative process.
11755.851 -> And at this point, the actual complexity of this is really straightforward to kind of
11760.581 -> foresee, we know that we're just going to iterate through an array of size n, that must
11765.721 -> mean that our time complexity is just n. In the same way, the only space we use is really
11770.521 -> just the space of the array, which I know is still going to be sighs and as well. So
11775.141 -> overall, we're looking at a linear solution for fib. So before we hop into the code, for
11779.13 -> this one, I just want to draw a really important connection. So although at face value, this
11783.66 -> iterative strategy looks very different from the recursive solution, a lot of the logic
11788.95 -> really carries over, I'm still using my overlapping subproblems to solve this one. So for example,
11795.32 -> I know that every index of this array really corresponds to some number input for fib of
11801.931 -> n, right? So I can kind of visualize it like this, right? Like we said, the six of bonacci
11807.61 -> number is eight. So I'm just going to choose a position of this table, let's say I looked
11810.601 -> at fib of six. So I know that to calculate fib of six, what I did was really add the
11816.021 -> previous two numbers into this position, right. And if you kind of see that the shape of this,
11822.79 -> I kind of dropped the table, and even just ignore the rest of the subproblems. This is
11827.91 -> basically just a tree that can kind of shift things around. And it really looks like this.
11832.42 -> So overall, you're looking at really the same relationship for calculating Fibonacci. It's
11837.53 -> just encoded in a table instead of the recursive tree. But overall, if you understand, you
11843.41 -> know, the recursive solution, you should still find, you know, some comfort inside of this
11847.811 -> iterative solution. But I think at this point, let's hop into the code for this one. Alright,
11852.92 -> programmers, let's go ahead and translate that strategy into some code. So I'll start
11857.46 -> by creating a table, which really just means creating an array, we'll call this table.
11862.881 -> And I need this array to have certain dimensions that are roughly the size of n. So to do that,
11868.28 -> in JavaScript, I can say array and call a static method and pass in my desired size.
11874.351 -> So here, I want to say n plus one. For the reason we did in the sketch, right, I need
11879.761 -> to make sure that the last index of this array is exactly n, I have to do a plus one here,
11885.7 -> because of course, indices start at zero. Cool, that should give me an array of that
11891.101 -> n plus one size. And then at this point, what I want to do is assign particular values into
11897.061 -> this table. So let's just say I console dot log, what this table looks like, I'll just
11902.181 -> run this first example of fib of six. So I'll give this a go. So notice that the array that
11909.61 -> prints out is this, it says like seven empty items, which means that the elements are undefined
11915.32 -> right now, according to our strategy, but we should do is assign these all to zero.
11920.55 -> So what I can do is, after I initialize the array over here, I can fill it up with all
11926.68 -> zeros using the dot fill method, right? It's an array instance method. So with that change,
11931.561 -> now I should have a seven zeros inside of this array. Cool, so it's looking pretty good.
11937.891 -> At this point, I also need to fill up a particular value inside of the table, I really need to
11943.141 -> make sure that the index one contains a value of one, right so I can say table at index
11948.98 -> one equals one. So we're seeding index one with a value of one to symbolize that the
11954.92 -> first bonacci number is one, remember that we kind of have like two base scenarios. In
11959.65 -> Fibonacci, let's just console that log with this table looks like now to make sure it's
11962.97 -> in the correct state before we actually iterate through it. Cool. So this is a good starting
11967.51 -> point for our table. So at this point, what I want to do is iterate through the table.
11972.141 -> So use a regular for loop for that. We're gonna need access to the indices. So I'll
11975.76 -> say let i equals zero. And I'll go up to and including, and basically give me iterations
11982.891 -> through the entire table. And I'll do i plus plus, I'm going to hit every position. Cool.
11987.61 -> According to what we did in our strategy. What I should do is look at the next two positions
11994.17 -> after I right, so I'll say as I'm gonna look ahead and table and I'll look at positions
12000.07 -> I plus one, and also i plus two, right? Of course, right now I'm currently situated at
12007.33 -> position I. And what I did was I added into those two neighboring positions, I added my
12014.68 -> current value in the table. So what I'll say is, in my next position i plus one, I'm going
12020.42 -> to increment it by exactly what table I says, right? So I'm incrementing, my next position
12027 -> by the value in my current position. And the same will hold true for the next position
12032.19 -> two spaces away, right, so they both get plus equals table. I
12037.04 -> know nice. So this is looking pretty good. After we're done doing this process for the
12043.311 -> entire table, where we're just going left to right, filling in our next two neighboring
12047.62 -> positions, what we'll do is just return our answer, which should be table at position
12053.09 -> n, right? To finish this off, I just want to return the table at index n, because the
12057.37 -> elements of this array correspond to the Fibonacci number, right? And the index is sort of the
12063.101 -> input to my function. So let's try these examples. We'll give them all ago.
12069.87 -> Cool. So I get 813 21. In this very large number, this actually looks correct. Nice.
12076.51 -> This is a fair implementation of Fibonacci. Notice that because we're already using an
12082.01 -> iterative strategy, we already satisfy a decent complexity for even this, this last example
12087.92 -> over here, we already mentioned the time and space complexity of this strategy, right,
12092.05 -> it gives us a linear time and space. However, you may already have in your head that we
12096.88 -> could actually optimize a little more here and just use a constant amount of space. However,
12101.75 -> if you want to kind of subscribe to just a generic tabulation strategy that I know is
12105.95 -> going to be useful in most problems, I will kind of stick to this overarching pattern
12110.26 -> right of initializing our table upfront, that is roughly the size of the input, and then
12115.55 -> iterating through that table. Of course, you can probably finesse this fib function to
12120.931 -> only track two variables represent your last Fibonacci number, as well as your last last
12127.13 -> Fibonacci number. But I'll leave that to you because it's not really a classic tabulation,
12130.96 -> which is a topic want to drill into our heads right now. Let's head back to the drawing
12135.45 -> board and do another problem. All right, now let's revisit that grid traveller problem
12140.36 -> using tabulation. So like before, we're going to have a 2d grid, and we start in the top
12144.931 -> left corner. And our goal is to go to the bottom right corner, we only have two possible
12149.25 -> moves at any point in time that is to move down or to the right. And how many ways can
12153.53 -> we actually travel to the goal, if we had a grid of dimensions, m by n. And we want
12159.302 -> to write our function to actually calculate this. So let's trace through how you can build
12162.939 -> a table for this one, let's say we're going through grid traveller of a three by three
12167.01 -> grid, that means that our output should be six in the long run. So like before, our first
12172.141 -> step in this tabulation recipe, is to create a table that is roughly the size of the input.
12179.311 -> And so here I have two inputs, and they really represent the number of rows and number of
12182.92 -> columns. So I can create a 2d array to correspond to that. Like before, if I want the indices
12188.44 -> to match up here, I'm going to have to make sure that this two dimensional array has dimensions
12193.07 -> four by four, right? If I give it a four by four array, that means that the bottom right
12199.29 -> index is really three comma three, which works out nicely, very similar to what we did in
12203.72 -> that last fib problem. Cool. So I established the dimensions for my table. Now I have to
12209.561 -> figure out what should I actually begin my table with, right? What are the good seed
12214.17 -> values to use here? Well, I know this is a counting problem, right? Because they're asking
12218.16 -> me to count the number of ways to travel through this grid. For these counting problems. Good
12222.811 -> initial value to choose is usually zero. So I'm going to put zero everywhere in this grid.
12227.7 -> But at that point, I may need to seed another value in other position in this table, if
12233.601 -> you recall our previous discussion about this grid traveler problem, we said that it's a
12237.681 -> really important case that all right grid traveler have a one by one should return one
12242.05 -> right there is one way to travel the gray one by one grid. And so I'll go ahead and
12247.37 -> actually take that information and encode it into my table. So I'll make index one one,
12253.44 -> actually contain the elements of one. And I should give me a nice starting point for
12257.9 -> this algorithm. So at this point, what we want to do is iterate through this table and
12262.45 -> come up with some logic that combines the values in this table, right, basically combining
12268.17 -> different subproblems to solve my larger problem at hand. So let's just begin in the top left
12273.891 -> corner of this grid. In the long run, we can really implement this kind of iterative pattern
12277.97 -> through a grid using just some nested for loops, right. And the key insight is I know
12282.66 -> if I'm at this position, if I see zero at indices, zero comma zero, that means in a
12289.05 -> zero by zero grid, there's zero ways to move through it, which makes sense, right? Because
12294.28 -> if any of your dimensions contains zero, then that must mean that your grid is empty, right?
12298.37 -> So the game isn't even valid. But if we want to have some consistent logic, the move is
12303.32 -> to use your current position highlighted in yellow right now and add it to your neighbors,
12308.79 -> right, according to the game, I can only move to the right or downward. So technically,
12312.932 -> what I'm doing right now is I'm taking my current positions element, and adding it to
12317.261 -> my down neighbor and my right neighbor. Obviously, if I just have a zero in my current position,
12322.811 -> then really no arithmetic takes place. But let's go ahead and do these iterations just
12327.51 -> to make sure we have consistent logic, right, so they keep adding these zeros to their right
12331.28 -> and down neighbors, nothing changes for now, until I get to the next row, right, this first
12337.271 -> iteration, next row, still just add zero to its down and write. But once I'm at this point,
12342.74 -> I'm going to take this one, I'm going to add it to my right and down neighbor. So that
12347.05 -> means they both turn into a one, I keep following this pattern, right, both of my neighbors
12352.32 -> turn into a one. And obviously, whenever I kind of reached like the bounds of my grid,
12356.751 -> I need to probably make sure that I don't do any illegal Array Operations. But that's
12361.04 -> really an implementation detail for the code. So on to our next row, we contribute zero
12366.68 -> to both of our neighbors. Here, we contribute one to both of our neighbors.
12371.69 -> Now we contribute to to both of our neighbors. And here we contribute three to both of our
12377.131 -> neighbors. And finally, things get interesting in the very, very last row, I contribute zero
12382.141 -> to my neighbors, I contribute one to my neighbor, I contribute three to my neighbor. And at
12387.94 -> this point, we've kind of finished iterating through the entire grid. And if you look at
12391.811 -> this position, it contains a six symbolizing that, hey, how can you actually travel through
12396.521 -> a three by three grid? Well, there are actually six different ways you could do that. So before
12400.7 -> we jump into the code for this one, you can probably already foresee the complexity of
12404.471 -> this. So we know that the complexity is really driven by the dimensions of this table, I
12410.33 -> know that this table is going to have m rows and n columns. So if I need to iterate through
12415.351 -> this table, it's just going to take m times n time. And along with that, what is the space
12421.001 -> that we need? Well, it's really just a space for this 2d array, which is, of course, still
12425.21 -> m by n. So overall have m times n time and space. Let's jump into the code for this one.
12430.83 -> Now. All right back into the code editor. Let's work on implementing this grid traveller
12435.45 -> tabulation strategy. So we'll start by initializing our table, which is going to be a little more
12440.54 -> complex here compared to our last function, because it needs to be a 2d array. So what
12445.551 -> I'll do is I'll start by creating, let's say, the correct number of rows. So that will be
12449.88 -> as simple as calling array. And I want really dimensions m plus one, again, I have to adjust
12456.011 -> for that off by one error, because I want the max index in this table to actually be
12461.461 -> exactly right. And I know indices start at zero, so I'm going to up it by one over here.
12465.971 -> Cool. So I'll give me the correct number of rows. And I want to make sure that the elements
12474.021 -> inside of this array are also other sub arrays. So a trick I can do for that is we're going
12479.711 -> to fill this is kind of just a very particular JavaScript pattern, I'm going to call fill
12484.271 -> on this array I just created. And afterwards, I'm able to map over it. Cool. So maybe I'll
12490.84 -> spread this out like this.
12493.721 -> And when I map over this array, what I want to do is make sure that every element of the
12499.11 -> array is going to be a new array like this, this time of dimensions n plus one though,
12505.45 -> so I have roughly m rows and n columns. So let's go ahead and just see what the shape
12511.03 -> of this is. When I print out, let's say the three by two example. So we'll go ahead and
12516.351 -> run this code. So notice that if I look at the arrays I printed out here, it looks like
12521.15 -> I have a four by three array, which makes sense because again, I increase my initial
12526.97 -> dimensions by one that looks good to go. However, what we'll want to do is really understand
12533.37 -> how what this map is useful for. So a very common mistake that you might be tempted to
12538.51 -> do is Can't I just, you know, skip this map part, and then do the fill with the array
12547.46 -> of n plus one. So that will look like it gives you the same thing. So I'll run that code.
12553.4 -> But if you write it this way, what you're doing is you're filling the entire outer array
12556.74 -> with a single array instance multiple times, right? So the shortcoming of this code is
12561.98 -> let's say I kind of change only one position of the table, let's say I just made an X kind
12566.65 -> of arbitrarily, that would actually look like it changed many positions of the grid, right.
12573.471 -> And that's because this is technically just one array that's inserted multiple times into
12579.45 -> the outer array, whereas I need a really unique array references. So that's how we need the
12584.65 -> map pattern here. I know every time we evaluate the callback function to map, it's actually
12589.61 -> going to execute this entire function, which means it's going to give us a new inner array
12594.74 -> instance. Right. So if I use this map pattern I run again, this is really the intended behavior,
12600.341 -> right? I have a unique array as a sub arrays here. And so let's take out this expert. But
12606.681 -> that being said, Now that I have the correct shape of my table, I need to insert some good
12612.351 -> starting values, right? So what are the seed values here, while we're calling in our strategy,
12617.94 -> we said that basically the entire table could contain mostly zeros. So maybe I'll bake that
12624.521 -> right into this line. So after I make a new sub array, I'll be sure to fill it with some
12632.17 -> zeros. So if I run that, now, I should have a nice table, because that's looking pretty
12637.91 -> good. At this point, I also need to see another value, right? We talked about it in that we
12643.88 -> should see table at position one, one with exactly one, this is sort of like our base
12649.761 -> case, when we have a one by one grid, there's definitely only one way to travel through
12654.141 -> it. So now we have the elements of one in position. One, one. Nice. So at this point,
12660.681 -> now I need to iterate through my table and fill in other positions. So I'm going to use
12665.311 -> just nested loops for this. So I'll say let's i equal zero. And I want to make sure that
12671.351 -> I goes up to and including m right, basically the dimensions of the table, and I'll do i
12676.311 -> plus plus. And very similar for my inner loop being sure this time to reference j and j
12683.32 -> is gonna go up to n this time, because we'll just have classic nested loops just iterating
12689.521 -> through this two dimensional array, right? So as I'm iterating through the table, what
12694.551 -> was the logic we trace through in our drawing? Well, I what I needed to do was take my current
12699.641 -> element that I'm at, so maybe I'll call it, let's say, my current. So my current element
12706.48 -> would be at table ij. So I need to take this current element, and I need to add it into
12713.51 -> my right neighbor, and my down neighbor, right. So if my row is I and my columns j, that just
12720.271 -> means I do some arithmetic on i and j. Right. So if I want to increment my, let's say, right
12725.97 -> neighbor, what I can say is, look at table, a position i, j plus one, that'd be directly
12732.521 -> to my right. And I'm going to increase it by whatever this current is, if I want to
12737.643 -> look at my down neighbor, and that'd be i plus one, but I keep j the same. Cool. So
12744.641 -> this will add to my two neighbors that are to the right of me and below me. And at this
12749.08 -> point, what we have to be aware of is like what happens at our edges of this table, we
12754.54 -> know that if we're already at like the last position of a row, if I do j plus one, I'm
12760.57 -> gonna go out of bounds. And so to make sure I don't step out of bounds on these increment
12764.88 -> expressions, I'll need some conditional logic. So I'll say if the right position is inbound,
12770.79 -> so if j plus one is less than or equal to n, then I'll go ahead and increment to the
12776.891 -> end should be okay. And likewise, if i plus one is less than or equal to m, then I'll
12783.972 -> also increment right? Notice that when I checked my like j value, that should be using n, right,
12790.17 -> because that's the number of columns and then I is a number of rows. So it should use M.
12793.971 -> Cool. So what I have so far as I'm iterating through every position of my table, and I'm
12800.762 -> going to take the elements at my current position, and I'm going to add it into my right neighbor,
12806.511 -> as well as my down neighbor, about only if they actually exist. Cool. So once I'm done
12813.58 -> filling up the table, then my final answer should just be at position m, and right just
12819.87 -> the very bottom right corner here. So let's give this code a run. So I should get 1336.
12826.65 -> And then there's a very large number, or run grid traveller.
12832.94 -> Nice and looks like this solution is totally working. And we are already satisfying our
12837 -> the last example here, right? If you tabulate off the bat, you will have a pretty quick
12841.36 -> solution. And we already traced through the complexity for this. So take a moment to you
12847.09 -> know, look at this code, let it sink in, or really try to understand how we took that
12851.101 -> whiteboard strategy and implemented it in some code, right? We do have to do some like
12855.88 -> kind of fine implementation details, especially with the bounds checking here. But I think
12860.62 -> the most important logic, it's about how our current position of the table contributes
12865.87 -> to our immediate neighbors to the right and downward. With that, I think let's head back
12870.18 -> into the drawing board. Alright, now that we've seen tabulation in two different problems,
12875.92 -> hopefully we're noticing some patterns right? Let's give ourselves some rules we can follow
12879.87 -> to tackle any dynamic programming problem using a general tabulation strategy. With
12885.23 -> this tabulation recipe, it's going to be pretty different compared to our memoization recipe
12890.88 -> in that there aren't two main steps when I talked about memorization I said, make sure
12896.041 -> you implement the brute force first, and then add the memorization app. Towards. Whereas
12901.061 -> if you just try to tabulate a problem, you're really going to have the most efficient version
12905.7 -> of your solution all in one swoop. And so what is our first step for tabulation? Well,
12911.601 -> of course, you have to visualize it as a table. And that really begins with a conversation
12916.581 -> of what you'd like the size or dimensions of the table V, that should definitely be
12922.271 -> correlated based on the size of the input. So in the case of fibonacci of n, we made
12928.45 -> our table roughly, you know, n elements long, sometimes you definitely have to watch out
12932.521 -> for like an off by one scenario, which we've been seeing lately. In the case of our grid
12936.721 -> traveler problem, we saw that because our, you know, problem represents a grid, we had
12942.42 -> to create a two dimensional table at that. So figure out the size of your table based
12948.53 -> on the inputs to the problem. Cool. And once we do that, we need to initialize some values
12955.43 -> within the table. To me, this is always about choosing compatible types. In other words,
12961.811 -> if your problem asks you to return a number, then it'd be wise to initialize the values
12967.581 -> of your table with numbers right? On the flip side of that, if I was asked to return a Boolean
12972.41 -> in a problem, Na, consider initializing true or false within the table. Cool. And once
12978.65 -> I have all of the kind of generic values filled up in the table, then I choose my very important
12984.641 -> seed value, right? I know that this seed value should capture the scenario, where I have
12990.561 -> a trivially small instance of the input where I automatically know the answer, right. So
12996.08 -> in the case of our classic Fibonacci, that means I just seed the N one and the N two
13002.88 -> values of my table with one and one respectively. And in the case of our grid traveller, we
13007.65 -> were sure to capture the scenario where we had a one by one grid, so you're gonna need
13013.05 -> to see those values, because that's the basis upon which we fill the rest of the table.
13018.53 -> And then we have the hard part, which is really iterating through the table. And you have
13022.29 -> to come up with some logic, right, as you iterate through the table, you have to design
13027.54 -> some logic that fills further positions of the table based on the current position. And
13033.44 -> you really have to, you know, look to the problem to figure out what the logic is. In
13037.311 -> the case of Fibonacci, it's as simple as if I'm at some position of the table, I look
13041.36 -> one space ahead and two spaces ahead, very reminiscent of the pattern Fibonacci, in the
13046.13 -> case of our grid traveler problem was about looking the unit to the right or the unit
13050.851 -> downward, effectively shrinking the size of our grid. So it's really up to you to figure
13056.79 -> out the logic, look for it language in the problem. What I always do is focus on what
13062.65 -> options I have at any point in the problem, right? My options are Do I go rightward? Or
13067.26 -> do I go downward in that grid traveler and problem. So we'll stick to these rules as
13071.83 -> we tackle our a tabulation problems, and we'll utilize them right now.
13079.43 -> Okay, so let's work on tabulating our can some problem now. So recall that in this problem
13086.91 -> we want to do is take in a target sum as an argument, as well as an array of numbers.
13090.68 -> And we want to return a Boolean indicating whether or not we could generate the target
13094.95 -> sum. By adding numbers in the array, we can reuse an element of the array as many times
13099.61 -> as we want, we can also assume that all the input numbers over here are non negative.
13104.87 -> So we've solved this problem before, right? recursively using memoization. But now we
13108.552 -> want to use a tabulation point of view. So let's come up with the strategy here. So let's
13113.86 -> say I gave you this input of seven and an array of 534. In the long run, the answer
13118.45 -> is true because it is possible to generate seven, right, you can either do a three plus
13122.891 -> four or a four plus three, and that would give you your original target. So you know
13127.59 -> that in the first step for tabulation, all we need to do is create a table. But I guess
13130.89 -> the question here is, what size table do I create, I have two inputs, I have the target
13135.57 -> as well as the array of numbers, which of those actually contribute to my initial table.
13141.62 -> And so the key insight is to think about, you know, what's going to change throughout
13144.87 -> the problem. In other words, if we can reuse the numbers of the array as many times as
13149.381 -> we need, then it's not like we're shrinking the size of that array. However, if we think
13153.891 -> about the target number of the target, some that were given, we do have a goal of actually
13157.93 -> reaching that target. So now we're going to increase to it over time. And so I'm going
13163.24 -> to use that as a basis for my table. That is, I want to create an array that is roughly
13168.681 -> the size of my target sum. So because the target sum is seven, I want to make sure that
13173.63 -> the indices lined up perfectly. So I'll actually have to create an array with length eight.
13179.24 -> And in general, I just be creating an array of length target sum plus one, because we
13184.15 -> have that classic off by one error over here. Alright, now that we have the correct size
13188.25 -> of table, what do I actually want to store inside? That is what should the eventual elements
13193.03 -> of this array be? So a really nice rule of thumb you can use for that is to just recognize
13198.11 -> what type your answer should be. In the long run, right, so here, they're asking for a
13202.08 -> Boolean answer. So that really tells me that I should probably put some boolean data as
13206.33 -> the elements of this array. So I'm going to start by initializing every element of this
13210.271 -> array with a false value. And the reasoning is right now we're kind of assuming before
13215.811 -> we check anything, that none of these target sums are actually possible to be generated.
13221.891 -> Cool. But neither think about any particular seed values, I need something inherent to
13227 -> this can some problem is we treat the target sum of zero very special, right sort of the
13232.271 -> base that we use. In other words, someone gave me a target of zero, I know that that
13236.541 -> is always possible, no matter what elements I have in the array, because to generate zero,
13241.4 -> I can just take no elements of the array. So that's going to be a really important seed
13245.561 -> value here. So I'm going to populate index zero with the element. True. Cool. So now
13251.33 -> that we have some good seed value, now we want to think about how we can flesh out the
13255.851 -> other elements of this array, sort of in a linear fashion, right? So I'm gonna have to
13260.22 -> iterate through this table. So let's just start at the very first index. So right now
13265.48 -> I'm looking at index zero. And if I'm situated in any position of this array, then the value
13272.431 -> I'm currently looking at, should basically say, you know, is it possible to generate
13276.44 -> that index amount? In other words, since I have a true at index zero, then I know that
13281.101 -> it is possible to generate zero using the coins of the array. But now that I'm at this
13286.44 -> position, how can I actually transition into the further positions of this table. And so
13291.57 -> what I want to do now is consider the possible numbers that I can take into my sum. So I'm
13296.181 -> going to start by looking at this first element of five. I know that if it's possible to generate
13301.28 -> zero, then if I also have a five in the array, then it's also possible to generate five in
13306.851 -> the context of my array, that means I should look about five spaces ahead from my current
13311.79 -> position. So I look at this spot, right, exactly five indices ahead. And right, now I should
13317.22 -> actually replace this false value that's stored at index five, with a true and again, the
13323.271 -> reasoning is, if it's possible to generate my current amount, right, right, I have a
13327.75 -> true in the zero, it's possible to generate my current amount, and I could take a five,
13333.24 -> that means that the position of five steps later should also be true, right. And I want
13338.51 -> to actually continue this process for the other values in my numbers array. So I'm just
13342.34 -> going to look at the next element of three, that's basically telling me that, hey, the
13347.311 -> next spot three places away should also become a true, so I'll just go ahead and make that
13351.42 -> a true. And finally, likewise, for the last element of four, right, the element four spaces
13356.04 -> ahead, should also be turned to true.
13359.351 -> Cool. And at this point, I think I finished like my very, very first iteration, that is
13364.36 -> when my current is at index zero. And at this point, I just want to keep iterating through
13369.421 -> the array, so I'm just gonna shift my point of view, or one space to the right. So now
13373.921 -> I'm looking at my current as index one. And what does that over here, my current element
13379.26 -> is actually false. So what that means is, it is not possible to generate one using the
13385.842 -> numbers of the array, which logically makes sense, right, I only have five, three and
13389.4 -> four. So there's no way you can ever give me back a one, right. So if I have a false
13394.58 -> value at my current position, then I should not modify like my further values by looking
13399.93 -> ahead. So notice that if I look at my edge for five, look, five spaces ahead, I am not
13405.431 -> going to make that a true because my current position is not true, right. So I'm going
13409.69 -> to keep iterating. Same thing happens when I'm at index two, because the current value
13413.99 -> is false, there's nothing to be done here, things get really more interesting on this
13417.04 -> iteration right? at index three as my current, then I see a true at this position. So that
13423.15 -> means I need to look ahead to my future positions, and actually assign them to be true. Obviously,
13428.601 -> you'll notice over here, if I look five positions ahead, when I'm currently at index three,
13433.101 -> then that would actually be out of bounds. So I don't need to look that far ahead. So
13436.471 -> we'll kind of drop that connection. But at this point, I should make the space three
13441.51 -> positions ahead truth, that's almost a space for positions ahead. So that means that indices
13446.25 -> six and seven are set to true, right? I keep doing this process all the way through the
13452.351 -> entire table. Notice that as I keep iterating toward the right, these forward looking references
13458.43 -> kind of stopped being useful because it goes out of bounds. But the point is, by the end
13463.08 -> of all of these iterations, I do have a true stored at index seven, which means that it
13468.19 -> is possible not to generate a quantity of seven. And if I do a quick spot check, and
13472.36 -> I look at just my entire table right now, it has very consistent data. In other words,
13477.12 -> if I notice where all of the true values are right there at 03456, and seven, that means
13482.4 -> that all of those quantities for target are actually possible, right? On the flip side,
13487.58 -> if I look at the only elements and indices that are false, like one and two, that makes
13492.39 -> sense, because those are the only quantities that are not possible or you can't possibly
13495.97 -> generate a one and two, using a five, three and four as your option. Adding up, right?
13501.561 -> So we know by the end of this algorithm, the element that is stored at the last position
13506.26 -> of our table would be the final answer, right? So if it's true, then it's possible if it's
13510.53 -> false, it is definitely not possible. All right, so let's wrap up this sketch by actually
13516.09 -> talking about the complexity of this, as always, we'll define our terms. So we'll say that
13520.62 -> n is going to be the target sum, which is just a number. And the flip side of that will
13524.83 -> say that n is going to be the length of the numbers array. And we already have the vibe
13529.62 -> that, hey, both of these terms definitely affect our complexity, right? If you give
13534.03 -> me a larger target sum, that's probably a harder thing to calculate. In the same way,
13538.72 -> if you give me more numbers to choose from, it's even harder to calculate, right. And
13543.011 -> we can recognize that the algorithm just has us flesh out this table. And I know that the
13547.931 -> table is of size m, so we're going to have at least M. However, as I iterate through
13552.751 -> every position of the table, what I had to do was actually look ahead in the table for
13558.28 -> every single element of the numbers array. So I know that that's going to be accomplished
13563.38 -> basically, using some nested loops, where I'm going to have a loop to iterate through
13567.19 -> just the table, and then a nested loop to iterate through every number of the numbers
13572.07 -> array. So overall, I'm looking at N, N times N, time complexity. And along with that, where
13577.211 -> does this space here? Well, the space is really just the exact size of this table, which is
13581.411 -> based solely on M. So I have just m space overall. Nice. So this is already looking
13587.28 -> like an efficient implementation, especially because we're not dabbling with any exponential
13591.43 -> complexity over here. I think let's work on the code now. So here we are back in the code
13596.87 -> editor, let's start like we usually do by creating our table. So I'll store this table,
13603.101 -> and like we discussed in our drawing, we need to make it roughly the size of our target
13606.811 -> sum. So I'll create an array, I need to make sure that the indices lineup directly or at
13610.93 -> one index, that is exactly the target sum. So I'm going to initialize it to target sum
13615.29 -> plus one, right. So that means if I pass in target sum is seven, when I create an array
13620.89 -> of length eight, which means that its very last index is seven, which is perfect. Cool.
13625.95 -> And while we're here, we'll go ahead and actually initialize this array with all false values.
13632.47 -> So I'll do that. I need to make sure that I seed index zero with a true symbolizing
13638.76 -> that the target sum is zero, it's always possible to be made, even before I look at, you know
13643.79 -> what's in the numbers array. So I'll just go ahead and say, table at index zero is equal
13648.75 -> to true.
13649.75 -> So let's just console dot log, what this table looks like so far. And I'll just run, let's
13655.561 -> say the second example here. So I should see my array of length beat, up need to go into
13663.601 -> this tabulation folder. Now I'm going to run this guy. Cool. So it looks like a good table,
13669.28 -> right? eight elements. And I do have a true at index zero. Nice. So at this point, now
13675.171 -> I want to actually lay out the core algorithm by iterating through the table, somebody who's
13681.39 -> a regular for loop here, let's say let i equals zero, go up to I can even include if I wanted
13689.12 -> to, up to an including the table dot length is you could also say a target some here some
13696.271 -> thing based on target some I'll do i plus plus. And here I'm going to choose to iterate
13701.141 -> using, you know the manual index, because I know I have to do some arithmetic to look
13705.24 -> ahead on the array. But as I iterate through every element of the array, I want to actually
13713.771 -> check you know if this position is true, so I'll check if table at index AI is true. Cool,
13721.8 -> they know inside of this if statement, I need to basically jump ahead or look ahead, based
13727.41 -> on what's in my numbers array. But I only need to do that if my current position is
13732.09 -> true, right, I only got to look ahead, if it's even possible to get to this current
13736.88 -> amount, right, the current amount I'm sort of tracing through would be at index i. So
13742.42 -> at this point inside of my if statement, what I want to do is now look ahead for each a
13746.4 -> number of this array, so a nested loop here. And I just want to look at the elements of
13751.73 -> this array. So I'll say for, let's say, let num of numbers. So I'm grabbing each element
13758.57 -> this time. And now for this number element, I want to look that many spaces ahead. So
13765.04 -> I want to look at basically table at index i plus the number. Cool. So imagine that I'm
13771.49 -> on like the first iteration of this, I know is going to be zero. So that's the very, very
13775.811 -> first index of my table. And if my num is five, then I'm going to do zero plus five
13780.72 -> looking five spaces ahead right just like we did in our trace. And for this position,
13785.96 -> that is like five spaces ahead. I want to assign it to be true, basically saying that,
13791.34 -> hey, if my current position is reachable, and I can take a step of exactly numb then
13797.94 -> that must mean position. I plus numb is also reachable write that entire for loop goes
13803.2 -> inside of this if statement, right? So this code is looking pretty good. So far, I think
13809.18 -> the last thing we need is just to return our final answer. After we're done iterating through
13814.22 -> the table. So here I'll just return table at index targets. Cool. So let's, let's go
13824.351 -> ahead and run these examples. Maybe we'll have to debug some stuff. Looks like I have
13829.61 -> a probably an infinite loop. Yeah, my program crashed with some massive error. So let's
13834.61 -> debug this one together. There's something that I'm not considering in my code. But we
13839.66 -> did consider when we drew it out. So something that is probably breaking right now is having
13845.221 -> to do with like the bounds checking. Something that's kind of unfortunate in JavaScript is
13850.84 -> this characteristic, this may have different behavior, depending on the programming language
13854.89 -> that you're following along in. But I know in JavaScript, I had some array, let's say,
13859.07 -> array, and I set it equal to just a handful of elements, let's say a, b, and c, if you
13865.261 -> actually do like array, at some out of bounds index, like index, I don't know 10. And I
13870.83 -> assign it like x, that will actually change like the effective length of this array. So
13877.771 -> if I take a look at what happens here, so I'm just running this kind of offhand example,
13882.36 -> when we run this, notice that we have about seven empty items before we have the X. Basically,
13888.63 -> when you do this type of weird assignment in JavaScript, it's going to like lengthen
13892.61 -> your array. That way, it makes sure that this Hey, new element of x is placed at exactly
13898.19 -> index 10. And so why is that relevant? Well, in my code, here, I don't really check if
13903.49 -> I'm out of bounds, right, and I just assign to something that's potentially outside of
13907.11 -> the current length of my array of my table, rather. And if I do that, then this condition
13913.37 -> is a little suspicious, right? Because if I do this assignment on line nine to an out
13917.3 -> of bounds position, then the length will get longer. And that's sort of like a circular
13922.03 -> argument, right? I keep iterating, only to make more stuff out of bounds, only to lengthen
13927.42 -> the table even more. So that's why I ended up with like an infinite loop over here. One
13931.91 -> way I can fix that is to just maybe limit this, so I won't make a table outline that's
13937.041 -> a little too dynamic. I know, I can basically stop once I reach the targets. So that might
13943.32 -> actually be the better move over here. Pretty, pretty fair bug though.
13948.96 -> With that, let's go ahead and run this code again. give that a shot. Here we see true
13955.021 -> true, false, true and false. And that is the correct answer over here. Nice. So here's
13961.45 -> a nice tabulated solution for our khamsum problem. And we already spoke about this code
13966.16 -> having n m times n, time complexity. Now it's really obvious, right? Because I can see how
13971.931 -> these exact the loops. And our space complexity is simply based off of M right. Cool. With
13978.021 -> that, let's head back into the drawing board for another problem. All right, now let's
13984.631 -> work on the house some variation of this problem. So like before, I'm gonna take in the target
13988.99 -> some as well as an array of numbers. But this time around, what I want to do is return an
13992.641 -> array containing a combination of elements that actually adds up to the target sum. Right?
13997.7 -> So here, I want to return exactly one way if it's possible. And along with that, let's
14002.2 -> say that it's actually not possible to return the target sum, then I should just return
14005.82 -> the null value. And if it's the case that there are actual multiple combinations that
14009.87 -> can generate the target sum, I can return any one of those. So we expect the strategy
14014.86 -> for this one to be very similar to our last problem. However, we will need some nuanced
14019.07 -> logic to return the array now, right? Because I don't want just boolean data once return
14023.431 -> an array. So let's sketch this one out. Let's see I'm stepping through this example. Or
14027.569 -> my target is seven and my array is 534. For this input, a reasonable answer to return
14032.4 -> would be an array of three comma four, right? Because I can add three plus four to get seven.
14037.41 -> You can also I guess, switch around the order of these elements. So if you also returned
14040.45 -> four comma three, I think that would also be a valid answer. Point being, I just want
14044.66 -> to return at least one way that can generate the target. So we know we want to solve this
14049.45 -> one with tabulation. And so we'll just follow our recipe right? And I need to construct
14053.51 -> an initialize some table, we already know that the size of this table is going to be
14057.771 -> based on the target sum, so roughly seven elements. But we of course, have that classic
14062.68 -> off by one nature, right? Because I want the last index of this table to align with the
14067.17 -> actual number argument. And in general, I'm going to initialize this array with a length
14071.92 -> that is target sum plus one. Cool. So now that I have the correct shape of table, what
14077.67 -> do I actually have to initialize as elements inside of this table? Previously, like the
14082.46 -> Boolean version of this problem, we initialized everything as false, right. And the reasoning
14086.811 -> there was, before we actually try all the possibilities, we're going to just assume
14091.71 -> that it's not possible to generate every quantity. We just want to adapt that for this new type
14096.75 -> of problem. They tell us that if it's not possible to generate
14099.87 -> amounts, then we should just return null. So I think null is actually a great initial
14104.82 -> value to just put everywhere in your table. So at the start, we're going to assume that
14109.93 -> none of the values or none of the amounts are actually possible to be generated. No
14114.07 -> is sort of the analog for false in this rendition. Cool. So now that I have most of the values
14119.97 -> added to the table, now I need to figure out what's the appropriate seed value, I need
14124.131 -> some actual data to begin and get the ball rolling on this one. So like we expect, it's
14129.061 -> about having an amount of zero, right, no matter what if you're given an amount, or
14133.84 -> a target sum of zero, and no matter what array of numbers are given, you can always actually
14138.64 -> generate that quantity of zero, by just returning the empty array, right, you can take zero
14142.87 -> elements from the numbers array, and there you have an array whose sum is zero. So of
14148.021 -> course, that means at index zero of our array, we're going to initialize it with just an
14151.601 -> empty array. And at this point, what we want to do is, of course, iterate through our table
14155.87 -> and come up with some logic we can use to fill out further positions to the table based
14160.58 -> on our current position. So let's say we begin at index zero. So here I am. And I have to
14166.38 -> figure out which further positions of the table should I be looking at, we've seen this
14171.45 -> pattern before, I should just base that off of what I have as options in my numbers array.
14175.78 -> So look at my first number of five, that means I could look exactly five spaces ahead in
14181.23 -> the table at this position. And so if I know that my current position is possible, but
14186.351 -> I see that I have an array and my current position, I know it's possible. If this current
14191.701 -> position is possible, then a position exactly five steps later would also be possible, right.
14197.42 -> And so what I'll do is I'm just going to copy my current value into my further value. In
14204.061 -> other words, I want to put an empty array at position five, right. However, I also have
14209.771 -> to include the number that I'm using right now, right, I took a five to actually get
14214.74 -> to this position in the first place. So I'm also going to add a five into that array.
14220.04 -> Cool. If I do a quick spot check, this is a reasonable thing to do. Because if I look
14224.341 -> at the state of that index five, it contains an array of five, meaning that hey, you can
14229.53 -> return a sum of five by just taking a single five. And with this, I want to do the same
14234.44 -> logic for the other numbers of the numbers array, right. So if I have the three, I'll
14238.74 -> look three positions ahead of my current place. And I'll also start by copying over the array
14245.7 -> at my current position. So I copy over the empty array. And after I copy that array,
14249.43 -> I actually want to populate it with the number that I took to get to the spot in the first
14253.3 -> place. So I just go ahead and put a three in that array. And the same thing happens
14257.64 -> for before.
14258.931 -> So if you go to the next iteration, you see that our current element is null, that means
14266.23 -> that the amount, right in other words, our index amount, is actually not possible, it's
14270.8 -> not reachable, meaning we can't get an amount of one by adding up any numbers of the given
14275.82 -> array. So what I should not do is actually manipulate any further values from this position.
14283.21 -> So I just continue these base iterations. Same thing happens when I'm at index two,
14288.021 -> things get more interesting when I'm at index three, right, I see that the element here
14291.811 -> is not null. So it is possible to generate three. And so what I want to do is go ahead
14296.93 -> and look at my my forward values, and for copy my current array over. So I know I don't
14302.61 -> need to look five spaces ahead, because that would be out of bounds. So I'm going to look
14306.14 -> at the position exactly three spaces ahead in the table and copy over my current array
14311.04 -> like this, what I also need to do is make sure that I include the number that I'm using
14316.37 -> right now, which is three, right, because I'm looking three spaces ahead. So I end up
14320.07 -> with three comma three, at index six of my table, which is reasonable, right, I also
14325.8 -> want to do something very similar for the position four spaces ahead. So I'm going to
14329.96 -> copy over my current array, but then also include the four that I'm using up right now.
14334.771 -> So now have three four over here. Cool. At this point, you can kind of see where this
14339.271 -> is heading right, I've actually already populated index seven of my table, and three comma four
14345.93 -> is exactly a one way we could create the seven. So we could stop here. However, let's say
14351.24 -> that you continued the algorithm. So in the next iteration, I know that this space for
14356.98 -> the indices ahead would be out of bounds, so I don't need to look there. But according
14360.4 -> to our general logic, I would actually manipulate the spot of the table three moves ahead, right.
14367.62 -> And so what I'll do is if I, you know, follow the same exact logic, I'm being consistent,
14370.881 -> not really changing any of the rules here, I would copy over my current array into that
14376.101 -> position. So I basically overwrite index seven, with just an array of four. But then at this
14381.061 -> point, I would also be sure to include the number that I'm using right now, which is
14384.971 -> three, right, because I'm looking three spaces ahead. So I end up with four comma three,
14389.55 -> which happens to be like another way I can generate these seven. So it doesn't matter
14393.05 -> if you actually continued this algorithm, right? So if I continued on basically just
14397.851 -> keep iterating until I hit the very And and what do I know the element at index seven
14403.43 -> is exactly an array of four, three. Cool. So the point is you actually don't have to
14408.271 -> like exit early. As you iterate through the table, you'll be okay to just finish all of
14412.66 -> your iterations. And you'd still have a valid answer, right? Because in this problem, they
14417.03 -> just want any way to actually make the target. And if I take a quick look at my table, I
14423.13 -> think all of the stored values here make some logical sense. For example, if I look at index
14427.82 -> two, there's a null, meaning that it's not possible to make a two by using elements of
14432.51 -> the array. In a similar way, if I look at index four, there's a sub array of four, meaning
14436.771 -> you can just take one four, to make a some before. Similarly, you can take two threes
14441.99 -> to make a sum of six. And of course, you can make a seven by doing four and three. So now
14447.74 -> that we have a plan of attack, let's look at the complexity for this. So of course,
14452.12 -> the M will represent the target sum, and our n represent the length of the numbers array.
14457.94 -> So if we begin with just the time complexity, we know that we're going to at least iterate
14461.78 -> through the table and the tables of size m, right. So I have at least an M in my time
14466.96 -> complexity. But for every space in the table, or in every iteration of that loop, I need
14473.351 -> to also iterate through all of the numbers in my number input array. And so I can multiply
14478.84 -> by n over here. Nice. But at this point, now, let's say that I actually need to take a valid
14485.19 -> step here, then what I also did was copy over the array at my current position into that
14491.28 -> forward looking position, I know that a sub array will be at most of length m, remember
14496.751 -> that m is our target amount, the worst possible or largest way we can generate the target
14501.561 -> sum would be to have an array of exactly m ones, right. And so we'll say that in the
14507.59 -> worst case, we have to copy over an array of an additional length m, that means our
14511.72 -> time is really m squared times. And now let's come up with the space complexity for this,
14517.94 -> we know that the initial size of the table is definitely based on M. However, in the
14522.24 -> long run, we could be storing a sub arrays as elements. So this is going to actually
14525.93 -> be a 2d table. In a sense, I know that the size of any particular sub array here is also
14533.48 -> going to be m for the same reason we just said, and so we'd have a m squared space complexity.
14539.71 -> So overall, we're pretty satisfied with the complexity of this. Because it's polynomial,
14543.42 -> right? It's not an exponential quantity. So it should run in a reasonable amount of time.
14547.881 -> All right, I think we're ready to hop into the code for this one.
14552.04 -> Alright, programmers back in my trusty editor, go ahead and tabulate this one out. So we'll
14557.601 -> need to create the table to begin, right. So I'll create my table, and it should have
14561.5 -> roughly targets some positions in it, really, I need to increase it by one. So I'll say
14565.99 -> array, and I'll pass in target sum plus one, that way the final index aligns, I'm going
14571.59 -> to go ahead and fill this table up with all of those nulls. Right, exactly what we did
14576.4 -> in our drawing. What I also need to be sure to do is make sure we populate index zero
14581.84 -> of our table with an empty array serves as sort of like a base case, in that we know
14588.521 -> that the target sum of zero can always be generated, right, you can generate the target
14593.311 -> sum of zero by taking no elements from the numbers array, right? If you took the sum
14596.601 -> of this array, it would be zero. Nice. This point, we need to iterate through our table.
14601.92 -> So you've seen this code before. I equals zero, go up to and I won't mess this up this
14608.29 -> time, I go up to an including target some I'll do i plus plus. So so far, very similar
14614.44 -> in shape to our last problem. Now that I'm iterating through every element of the table,
14619.061 -> I need to add some conditional logic. If you remember, from my drawing, we only did like
14623.24 -> the heavy logic when our current position was not null, right? Because if it is no,
14629.15 -> you can't even reach this current position in the first place. So I'm going to check
14633.271 -> if my current amount is attainable. In other words, if table at index i, which is my current
14638.94 -> amount, if that is not know, if it's not know that I can generate amount I goal. So if I
14646.25 -> can generate amounts i that i know some other things could also be generated. That is, I
14652.351 -> want to look forward in my table for every number in the numbers array. So I'll say let
14658.771 -> num of numbers, so I'm getting the elements of the numbers, right? And I need to look
14664.29 -> that number of positions ahead right. So if I'm at i right now, that means that a forward
14670.3 -> looking position would be at Table A plus num. Cool, and I need to assign something
14676.43 -> over here. So I'm going to do is start by copying over the sub array at position I at
14683.391 -> my current position, right. I know that if table I is not know the only possibility that
14689.53 -> it could be is an array, right can be array of zero elements are going to be an array
14693.49 -> of many elements. So no matter what that array looks like, I'm just gonna copy over that
14697.65 -> array. So I'm going to spread out all of the elements table I. And so far like this logic
14703.141 -> is looking pretty good because I'm basing my assignment on table i plus num off of table
14709.051 -> I, right. But in addition to that, I also need to include this choice of num. So I'm
14714.7 -> just going to add that into this new array as well using some syntax that we've seen
14719.03 -> previously in the course. Right. So I'm just copying over all the elements of the sub array
14723.601 -> at table I, and adding on an addition to that my current number. Nice. So this code is looking
14731.18 -> pretty good. I think at this point, all we need to do is let all these iterations finish,
14735.76 -> and then wrap up by returning the last entry of the table, or really the table at index
14742.101 -> target some cool, so not that much different from our last piece of code. Let's give this
14750.37 -> a shot. So I'll run how some and here I have a few diverse examples. And notice that some
14755.271 -> of them should return null because they're not possible. If we check these 32243, no
14760.96 -> bunch of twos and another No, nice, it looks like this code is totally working. Notice
14765.802 -> that we already have a very efficient algorithm kind of off the bat kind of comes free with
14770.26 -> tabulation, we definitely have m iterations in this for loop, we have n iterations on
14775.181 -> the inner loop. But then we also have to consider this copying over logic, right copying over
14780.271 -> an array will be at most m. So it's m times n times M, which is just m squared n, exactly
14787.38 -> how we spoke about it before. It's something I really want to emphasize is, you know, in
14791.941 -> isolation, if someone just asked you to solve like this house on problem, I don't think
14795.98 -> it's an easy problem. However, it probably feels almost trivial, especially since we
14800.601 -> just did cancer, right? There's a really, really straightforward progression to these
14805.28 -> problems. If you compare the house some and can some functions, they're very, very similar,
14809.72 -> right, really just changing the type that we store inside of our table, as well as the
14815.23 -> assignment that we do when we transition into our further elements of that table. Really
14820.721 -> just adjusting that logic for the data type that they're asking us for. Right? I had like
14825.061 -> some array answers here. And here, I have some Boolean answers. All right, so we're
14830.91 -> smashing through all of these tabulation exercises, let's do one more variation of this target
14835.9 -> some problem, that way, we'll have a little bit of an increase in the difficulty,
14844.58 -> it's time to revisit the best sum problem. So we're going to take in again, the target
14848.41 -> sum as well as an array of numbers. But this time we want to do is return an array containing
14852.331 -> the shortest combination, that adds up to the target sum. And if we have any ties for
14857.521 -> the shortest combination, we can return any one of those shortest. So you take a look
14861.881 -> at an example kind of like we did before, we have eight and an array of 235, there are
14866.771 -> a few different combinations, I can yield eight, right, I can do two plus two plus two
14871.091 -> plus two, or I can do two plus three plus three, or I can do three plus five. And among
14876.19 -> these options, the shortest one is obviously the three plus five. So that should be the
14880.11 -> return value over here. Obviously, if you returned five comma three, that would also
14884.51 -> be a valid answer. Nice. So let's start tackling this with our classic tabulation recipe. So
14890.57 -> we need to decide on the size of our table, like we usually say it's going to be based
14894.53 -> on the target sum and not so much the length of the numbers array. And like we've been
14899.12 -> saying recently, it would be nice if the last index of a table aligns with the original
14903.67 -> target. So although the last index of this table is eight, that doesn't mean that its
14907.91 -> length would be nine, right? Let's start by setting the proper values here. I always want
14913.49 -> to begin my my value seed with the type that's kind of relevant to this problem. In other
14918.561 -> words, they want us to always return an array if there is at least one valid way to generate
14923.67 -> the sum. And I know that trivially if I wanted to calculate the best sum for zero, no matter
14929.64 -> what array of numbers, I'm given the answer, there's always an empty array. So I'm going
14933.91 -> to be sure to store that value at index zero of my table. And now for the rest of the values
14939.79 -> in the array, I can't be sure if I can generate them yet. So I'm going to initialize them
14943.96 -> as null just like our last problem. At this point, we can begin our general algorithm
14949.46 -> by iterating through the table. And so what I'll do now is look ahead for my current position
14953.431 -> based on the numbers of the numbers array. So I'm gonna look two spaces ahead. Right
14958.23 -> now I see that there's a no inside of that position, which means I haven't quite found
14962.66 -> a way to create two, but I'm actually finding a way right now, right, I can copy over the
14966.99 -> array from my current position. But I also need to populate it with how far I'm looking
14971.82 -> ahead by so just to inside, and do this for the other numbers of the numbers array. So
14976.61 -> something similar happens for three, as well as for the five. Notice that since I just
14981.711 -> began the algorithm, this is a really the first time I'm finding any solution for these
14986.6 -> further values. And so let's keep these iterations going. I'm going to shift my frame of reference
14991.88 -> by one since my current position, good things and all that kind of entails that one is unreachable,
14997.95 -> right have no way to generate a sum of one it's actually don't do any logic from this
15002.4 -> current position. So we move ahead to do, since I do have a non null value, right, I
15008.21 -> have an array over here, that means I should look ahead to my future values. So I start
15012.47 -> by looking two spaces ahead, I see that at index four, there's a null, which means that
15017.45 -> I actually need to replace that now. Alright, I'm going to copy over my current array. But
15022.021 -> then I also add the value for how far I'm looking ahead by. So now I have two comma
15027.761 -> two inside of that index of four. So now I look ahead three spaces, which means I'm analyzing
15033.05 -> index five, I'll notice that index five already has an array there. And if I actually continue
15039.24 -> my sort of standard process, I would consider this array, I would copy over my current array
15044.251 -> in yellow, which is an array of two, then I would also add the element I'm looking ahead
15048.63 -> by which is three. And technically two comma three is one of the ways I can generate five.
15053.79 -> But it's definitely not the shortest way right, I can basically compare the length of these
15057.73 -> arrays, and notice that the original array of just five is definitely shorter, so that
15062.42 -> one should get the stay in this spot. Cool, I'll do something similar for five spaces,
15067.25 -> I see five spaces ahead for my current position yellow, there's a no right at index seven,
15072.641 -> which means that I can just go ahead and put this new array inside, so I copy over the
15076.79 -> array of two, then I add the five, right, because that's how far I'm looking ahead.
15081.2 -> I keep this process rolling, I'm going to modify two spaces ahead.
15088.311 -> Notice that this sub array I'm considering would still be longer than what's already
15092.61 -> stored at index five. So I actually don't use the three comma to write the five gets
15097.101 -> the same. Now I look three spaces ahead. And this would actually be the first time I found
15102.061 -> at least a way to generate six. So I copy over my current array, and also add my lookahead
15107.99 -> value. And now if I look five spaces ahead, there's a Knoll currently in that index eight,
15112.62 -> which means that I'm actually finding the first way to generate eight, so I copy over
15116.88 -> my array of three, and then I add my five for my look ahead. Cool. At this point, we've
15122.07 -> actually already found a way to generate eight. And you can probably foresee that this would
15126.17 -> be the shortest way in the long run. However, if we kind of continued this algorithm, there
15130.73 -> would actually be nothing wrong with continuing it right, it's not like we're ever going to
15134.801 -> put something longer in that spot. So if we continue our algorithm as normal, if I look
15139.98 -> two spaces ahead for my current position, that would mean I'm considering two comma
15144.04 -> two comma two, to my array of three, three, and obviously, I just maintain the shorter
15148.54 -> one, so the three comma three gets to stay. If I look three spots ahead, then I would
15152.36 -> be comparing two comma two comma three to a two, five. And of course, the two, five
15156.69 -> is shorter, so it gets to stay. And this process really just continues for the rest of the
15162.811 -> array.
15174.49 -> By the end of this algorithm, we do have the correct sub array stored at index eight. So
15180.2 -> before we jump into the code, let's look at the complexity of this one. So we'll say that
15183.391 -> MSR target's m, and n is the length of the numbers array. And so we know that in terms
15188.84 -> of the time complexity, it's going to be the same as last time, right? Still m squared
15192.7 -> times n, we don't really add any additional like costly logic, all we're really adding
15198.13 -> into the mix is just some conditional logic, which I know is just a very simple if statement.
15202.551 -> So that's not going to add any additional time. And the same way, our space complexity
15206.6 -> is still m squared, right, because I have a table of size m. But I know every element
15211.63 -> of that table could be an array, really just a two dimensional array. I think with that,
15216.93 -> let's hop into the code for this. Back in the editor, let's go ahead and code this one
15221.94 -> out. So we'll start by creating our table. And I'll make this table have roughly size
15228.07 -> of targets. But really target some plus one that way the final index aligns. And when
15233.331 -> I actually initialize this table, I want to actually make sure I have no values inside.
15238.89 -> So by default, all elements will be no basically symbolizing that, hey, these quantities cannot
15246 -> be generated yet, we have to actually prove that there is some combination that can make
15249.75 -> them. But the really important seed value is to make sure that index zero is an empty
15254.78 -> array, right? Basically, the one guaranteed way I can make zero is by adding no numbers
15261.45 -> of the array. This point I need to iterate through every index of my table we always
15267.49 -> do. I'll go up to the end including why not the target some nice and as I iterate through
15276.5 -> every position of the table, I only need to do the heavy logic if my current position
15281.8 -> is not no, right. So if my table and my current position so table I that is not no then I
15291.82 -> need to do some additional logic. So just to review, why do I need to only do the logic
15296.8 -> if my current position is not know? Well, let's say that it wasn't All right, if my
15300.58 -> current position is null, then I don't need to bother considering any further moves from
15305.47 -> this position if this position isn't reachable in the first place, right? So only if I have
15310.141 -> a non null position, then I'll actually iterate through my options in the numbers array, or
15316.86 -> I'll consider possible moves. So let's say let num of numbers. And as I iterate through
15323.35 -> these additional numbers, and then I need to actually look ahead in the table basically
15328.21 -> by that number amount. So I need to write some logic about table at index i plus num,
15333.99 -> right, just looking forward in the table. And we know based on our previous problem,
15338.66 -> it can some we ended up doing this logic, we just replaced that position with a new
15343.79 -> array, where we took all the elements from our current position, so table I, we spread
15348.88 -> out that array. Then we added additionally, this current number that we're using, right,
15353.801 -> so we have to actually add that into our move list. Cool. However, now we want to actually
15359.28 -> add some like, min value logic, I want to prefer to always keep the combination array
15366.41 -> that is shorter, right, I want the best way, not just anyway. And so I think the move is
15370.811 -> or refactor this code a little bit. I'm going to actually do this step, maybe separately
15377.23 -> above. So I'm going to save this to a variable. And I'll call this my say, combination. This
15384.43 -> is like my candidate combination, I'm not sure if this one is actually the shortest
15388.35 -> right now. And so if I do the assignment over here, then I'll just use combination. But
15394.5 -> I only want to do this assignment on line nine, if a condition is met, right, so what
15398.45 -> I want to express is, like, if this current combination is shorter, than what is already
15409.97 -> stored, right, I only get to replace something if it's shorter. That's pretty straightforward
15415.271 -> to express and an if statement, right? So I want to check, hey, if. And my condition
15420.18 -> needs to be if the thing already there. So if the element at index i plus num. If that
15428.45 -> thing is actually bigger than my current combination, so combination, not length, and I also need
15436.34 -> to be sure to do that element dot length, right? If my combination is shorter than my
15441.74 -> current thing being stored in at that future position, then I'll go ahead and replace it.
15446.86 -> Nice. So this code is looking pretty good. There's one thing that's kind of missing,
15452.721 -> but I think we'll just run it and we'll kind of debug it together. So let's say at the
15455.99 -> end, I return my table at index Parkinson. Cool. So compared to our last code, we only
15463.99 -> added this conditional logic. So if I run this, something's gonna go awry. And kind
15469.771 -> of foresee
15470.771 -> it's very an error. And the error message is pretty on the nose over here, it's saying,
15476.84 -> alright, cannot read property length of null. Right. So it's really about, particularly
15481.75 -> this expression. So we know that our initial state of the table has a bunch of null values.
15488.14 -> So when I look ahead to a future value, and for the first time, I actually have a way
15492.39 -> to make it, then I'm going to check the element, which is no dot length, or I cannot do null
15497.801 -> dot length. Right. And so I need a way to kind of get around that. If ever, I have like
15502.78 -> a null value in my table, then I can just go ahead and do the assignment, right, because
15507.05 -> I found at least one way to make that. So what I'll say is I can put an order here.
15513.36 -> So I can still do the assignment will say not table, i plus in JavaScript knows a falsie
15524.62 -> value. So let's say table at index i plus num is no. So this expression is no which
15530.2 -> is really false. So if I say not false and evaluates to true, so I'll go ahead and still
15535.71 -> assign that combination to that position, even if it isn't all, another great reason
15539.32 -> for using this syntaxes. We know toward like the tail end of our algorithm, we're going
15543.94 -> to potentially look out of bounds, which means that these elements could be undefined and
15549.63 -> undefined is also a falsie value. Right? So here I'm saying not false, which triggers
15555.66 -> as true, which means I would still correctly assign those positions. Right. The important
15561.12 -> thing is I need to be sure to not do this expression. When the element is null or undefined.
15568.5 -> You can't say no dot length, and you also can't say undefined, not length. So this expression
15573.92 -> will guard against that. So with that change, let's go ahead and run this. Cool. So 73544
15583.811 -> and a bunch of 20 fives nice, especially looking at that last example, like 20 fives is obviously
15589.98 -> the best way to make 100. And just to be super Sure, should be no different if I even switched
15596.73 -> around the order of these sort of ways. So what I put like, one, five in two. That should
15604.6 -> still give me a bunch of 20 fives, right? Because it's really looking for the best way
15608.44 -> to generate each some, which would mean the smallest summary. So there we have it a pretty
15613.771 -> interesting tabulation a problem. However, I do want us to recognize how similar it is
15618.82 -> to our last how some problem, right? So here, I have best some on the left and how some
15623.391 -> on the right, notice how similar the code is. Really, the only difference is this if
15628.271 -> statement, right? For the best some problem, I just added this conditional logic. And all
15633.391 -> this does is prefer to keep the shorter combinations over the longer combinations, right, because
15639.42 -> that's what I want to return in the long run. Whereas in houssam, we don't really have any
15643.73 -> preference in either direction. Awesome. So let's go ahead and go back to the drawing
15647.851 -> board, I think we'll kind of switch things up. Let's switch gears a little bit and revisit
15653.12 -> those string problems with tabulation. Let's begin with that can construct problem. So
15657.13 -> we're going to take in a target string as well as an array of words in a word bank.
15661.851 -> And this version of the problem and I want to do is return a Boolean, right, true or
15664.511 -> false, indicating whether or not I can make the target by concatenating, any number of
15669.07 -> elements from the word bank array, and I can reuse elements in the word bank as many times
15673.061 -> as I want. So we've seen this problem before. But just to refresh, let's take a look at
15677.076 -> an example. So let's say my target was abcdef. And I gave you this array of some different
15683.18 -> substrings. In this particular scenario, it is possible to create abcdef using members
15689.15 -> of the array. So the answer here is true. And one way you can actually create your target
15693.95 -> string is by just doing ABC plus the fright. Alright, let's start to apply our tabulation
15699.811 -> to this one. So I'm going to create a table and you probably already recognize that, why
15704.61 -> I want to base the size of this table off of the target string, right, because that's
15708.802 -> really where I have some problems, I'm going to be decreasing the size of my target, or
15713.061 -> in other words, building up to my original target over time. And so I know that I'm going
15718.28 -> to make it about the target string and not the array of words, because I'm never going
15722.58 -> to remove a word out of the word bank. So I'm gonna start with this table. And here
15727.391 -> the sizing is is kind of particular, because we need a way to have this table or represent
15734.17 -> information of a string, right. Whereas before with our number problems, it's very straightforward
15739.8 -> to just use the index as the actual number. So we're going to use a little clever have
15745.01 -> an encoding here. And so what you'll notice is here, I created an array of size seven,
15750.53 -> right, table dot length is seven right now. And in general, you'll want to size your table
15755.311 -> by doing target dot length plus one. So we still have that sort of off by one scenario,
15759.67 -> that's kind of a very characteristic of these tabulation problems.
15764.521 -> And so what will this table represent? Well, if I just think of the characters of my original
15770.09 -> target string, I know they have corresponding indices sort of like this, you're probably
15775.62 -> wondering, then what's the point of having that extra spot at the very end of the array,
15780.49 -> right? What I need to store index six, if it has no corresponding character. And the
15786.47 -> reasoning is, we need a way to actually represent information about the empty string. And so
15791.14 -> here's the pattern we're going to use. If I look at index zero of this table, then I'm
15796.75 -> actually making a statement about the empty string. So pretend that if you look at a spots
15802.431 -> of this table, depending on what that index is, you're considering the substring, that
15807.272 -> starts at index zero, and goes up to but not including your current position. So we'll
15812.97 -> see how this scales over the rest of the indices here. So imagine that now is at index one,
15818.93 -> I'm looking at index one, and you're really just looking at the A string, for at index
15824.09 -> two, then you're looking at a B, if you're at index three, then you're looking at ABC,
15828.71 -> for index four, then you're going A through D, if you're at index five, you're looking
15832.62 -> at A through E. And so the really key insight here is if I'm looking at index five of my
15837.94 -> table, it means I'm making some logical statement about the substring starting from index zero
15844.34 -> going up to but not including index five, right? So if I wanted to make a statement
15848.83 -> about the entire target string, then I should actually look at index six, right? Because
15854.71 -> imagine you start your substring at index zero always, and you go up to but not including
15859.58 -> your current index. And again, the whole reason for this is I need a way to actually make
15865 -> a statement about the empty string.
15867.94 -> And so speaking of the empty string, let's go ahead and talk about our C values. We know
15873.061 -> that a really core you know pattern in this can construct problem is to use the fact that
15878.521 -> hey, if your target string was empty, no matter what array of words you were given in your
15882.63 -> word bank, that is always possible. So it's always true. And so that means at index zero
15888.99 -> of my table, I can go ahead and seed a boolean value. How do I know what type to even store
15894.5 -> as the elements of my table? Well, if the problem is asking me to return a Boolean,
15899.021 -> then it's probably a good To actually just begin with some boolean data in your table.
15903.681 -> So if index zero is going to start as true, I'm going to initialize the rest to be false,
15907.771 -> right. So I'm going to consider all of these other sub strings or prefixes as not possible
15912.681 -> until I prove them otherwise. Alright, so let's begin our general algorithm. So we'll
15917.551 -> begin like we always do by just iterating, from left to right through our table. And
15921.451 -> just we have some notation to make it easier to kind of translate the information in a
15925.371 -> table to the statement it makes about our target string, I'm going to have the characters
15929.131 -> up top, and I'm going to like them up based on the substrings that we're really considering
15933.36 -> here. So if I begin at index zero, like we said before, that means I'm making a statement
15938.64 -> about the empty string, right, I see a true inside of this position, which entails that
15944.1 -> the empty string can be constructed using the word bank. Cool. At this point, I have
15949.25 -> to figure out how I can look ahead to my further values in the table. And I know that I can
15954.641 -> do that based on the choice of words in the word bank. So let's look at one of our words
15960.19 -> like a B. So something great about this option of a B is it actually is something that begins
15966.98 -> from my current position, right. In other words, if you look at my current position
15970.54 -> in yellow, the characters below it start with a and follows with me. So taking an AB right
15975.71 -> now would be a totally legal move, right those characters align. And so what I'll do is,
15980.641 -> I'll go ahead and look two characters ahead, by look two characters ahead in my table,
15986.011 -> that just means look two indices ahead. So now I know that this position of the table
15990.08 -> is actually reachable by just taking a B. So I put a true here. And now at this point,
15995.37 -> I would do this same logic for the other words that have their characters aligned. In other
16000.521 -> words, ABC is also some matching word. So I'll go ahead and make that a true. And ABCD
16006.12 -> would be the only other one that actually aligned its characters. So I'll make that
16009.73 -> one true. Again, how do I know to actually put this information in the table, it's really
16014.79 -> just the length of the word right, my current position right now is at index zero. And I'm
16020.101 -> looking at a word that is four characters long. So that means I just look for characters
16024.021 -> ahead to store my Boolean. Cool at this point, I'm actually done looking at all the words
16029.78 -> for this current position. So now I can move my current ahead one. So now I'm at this position.
16035.87 -> And so what you'll notice is in this current position at index one, we have the value of
16040.36 -> f, which means that it's false, right? It is not possible to actually generate in a
16045.97 -> write, there's no way you can ever just give me back an A, because all of your substrings
16051.18 -> in the word bank are too long, right? So I don't actually need to do any of my heavy
16055.94 -> logic from this position. However, on the next iteration, when I have something that's
16060.4 -> true, then I need to do my heavy logic. And if I do a quick spot check, I have a true
16065.43 -> stored in this position to write if I'm looking at the value stored at index two, that means
16070.62 -> I'm making some statement about indices zero up to but not including tomb. So really just
16075.551 -> the substring AB. And I know that if it's true, that means it's possible. And looking
16080.12 -> at the elements of the array, it's definitely possible, right? You just took a beat even
16083.53 -> get to this position. But I need to look ahead. So I'm going to look at any words of the word
16089.471 -> bank that match starting from my current position. Really, the only option here is CD, right.
16096.12 -> So if you look at my going characters, right now, that means I can take this CD. And so
16101.17 -> what I'll do is I'll look two characters ahead and place a value at that position. That's
16106.55 -> true. But the value stored at this position of the table is already true. So nothing really
16110.74 -> changes here. And there are no other words of the word bank that actually begin with
16114.311 -> a C. So I'm actually done with this pass. So now I move my current again. And I look
16120.42 -> for any matching words that begin with like a D, and potentially other characters after
16125.35 -> that, really, the only one here that works out is D, F. And so looking at my going characters,
16130.99 -> now I'm basically considering this chunk, right ABC followed by TF. So I'm going to
16136.49 -> look three positions ahead, because the word I'm considering right now is three characters,
16140.971 -> right def is just three characters. If I do that, I'd be looking at this position. And
16146.051 -> I'd be making this one a true now basically saying that it is possible to construct abcdef
16153.36 -> colon at this point, I would do the same thing for the rest of the table, we know that nothing
16157.7 -> else is really going to change within this table.
16160.561 -> And so by the end of the algorithm, we have a true start at index six, which means that
16166.311 -> our entire string is possible to be constructed. Right? And so if we do a quick sanity check,
16171.49 -> if I look at the table at index six, that means I'm making some statement about the
16176.59 -> substring that starts at zero and goes up to but not including index six, which would
16180.931 -> really be the entire A through F. I know that it's true. So that substring is possible to
16187.23 -> be generated, right, which was our final answer fairly elsewhere. Like maybe at index five,
16192.45 -> there's a false here, which means that I'm making some statement about indices zero through
16198.61 -> four, right, so ABS C, D E. And since there's a false at this position, that means that
16203.191 -> it's not possible to generate this string. And looking at just the elements of the word
16208.19 -> bank, it's definitely not possible to ever get a, b, c, d, e. Cool. So the data within
16213.61 -> our table a does look consistent with that, let's actually talk about the complexity.
16219.58 -> So we know that m is going to be our target string, whereas n is going to be the number
16223.521 -> of words in the word bank. And so we just consider the time complexity right now, we
16228.19 -> know that we're at least iterating through every element of our table, and our table
16232.98 -> was roughly size M, right? However, once we were at some current position of a table,
16239.08 -> we had to look ahead based on all of the words in the word bank. And so that would be an
16244.55 -> N operation for every iteration of m. And even still, as always, considering the words
16250.59 -> coming from my current position. And to make sure that their characters aligned, right,
16254.54 -> I can't just take arbitrary words, from the word bank, I have to choose the ones that
16258.69 -> have their characters matching right now, I know that the maximum length of any word
16263.17 -> or the maximal length of a match, I would have to do would basically just be in terms
16267.24 -> of M, so give me an additional m term. So overall, I'm looking at m squared n for the
16272.79 -> time complexity of this one. And besides that, we already know that the space complexity
16278.23 -> comes from the table, which is just a array of Boolean, so just m space overall. And so
16284.311 -> from a bird's eye view, it looks like this strategy would be an efficient one, right,
16287.35 -> we have a polynomial time complexity, but more importantly, a non exponential complexity.
16292.69 -> So let's code this one up now. Alright, let's code up this can construct problem. So we'll
16297.391 -> start by creating our table. And so we'll say create a new array of size target dot
16303.19 -> length, plus one just like we spoke about in our drawing, then from there, I need to
16308.68 -> give some initial values to my table. So I'm going to go ahead and fill this one up with
16313.561 -> all falses. So we're going to assume that every like prefix of our target cannot be
16319.47 -> constructed until we prove it otherwise, then I need to actually also initialize table index
16324.311 -> zero to be true, symbolizing that the empty string can always be generated, right? Then
16330.39 -> from there, I need to iterate through every index of my table. So I'll say i equals zero,
16335.13 -> just go up to the entire table. And here just to be safe, I'll say is less than or equal
16340.71 -> to target dot length. Nice. And as we iterate, we actually need to check our current positions
16350.01 -> value. Recall that in our strategy, we said we only need to do like the heavy logic, if
16355.63 -> our current position is reachable, so our current position should be true. So I'm gonna
16359.91 -> go ahead and check Hey, if my table at index i is true, then I'll do some additional logic.
16370.95 -> If I enter this if statement that I need to consider any further choice of words I can
16375.771 -> use to progress to other spots in the array, right? So over here in this if statement,
16380.75 -> I want to iterate through every word of the word bank, right? So I'll say four letter
16385.779 -> word of word bank. Nice. And so what I want to do, as I iterate through all my possible
16394.539 -> words is I need to make sure that these words I iterate through actually match their characters
16401.529 -> to my current spot that I'm at. In other words, if I am at my very first index of a over here,
16409.92 -> then when I iterate through all the options of words, I need to make sure that I only
16413.35 -> use a valid words that match like a, b, or ABC, or ABCD. Right? There's no point of me
16419.729 -> trying to use def right now. Right? I need to actually kind of narrow down my words,
16425.75 -> based on whether or not they match characters, right. So to do that, I can check pay if.
16432.75 -> And what I want to express is, you know, if the word matches the characters, starting
16439.959 -> at position, I
16440.959 -> position I goal, it's not going to translate that into some code. Well, checking if a string
16448.92 -> matches to something straightforward, just about using equality, right? But I have to
16452.789 -> figure out how can I look at the correct you know, substring of characters within my original
16457.84 -> target, right? It's pretty straightforward. All I need to do is say target dot slice.
16463.609 -> I'm going to slice starting at index i. And how far do I want to go? I basically want
16469.819 -> to go like the length of my word. So the move here is to say go from I up to i plus word
16478.109 -> dot length. And so let's kind of break down this logic. So what it's trying to do is check
16484.289 -> if your word matches your characters starting at position I. So let's check an easiest scenario.
16489.93 -> Let's say that my current position is zero, right? So right now is zero. That means I'm
16494.039 -> at this position like this ABCD f example. Let's say right now that my word is ABC. So
16500.84 -> if I look at this slice, it's going to slice starting at index zero, up to and not including
16506.738 -> zero plus three, right, the length of my word is three, zero plus three is just three, that
16511.719 -> would actually look at this run of ABC. Good. And let's say we were somewhere further in
16517.379 -> the string. Let's say we were actually at this D character over here. And so if I'm
16522.869 -> at this D character, that means my value for AI is going to be three Wragge, oh 0123. And
16530.01 -> the current word I'm considering, let's say is this def, the length of this word is three.
16535.209 -> So this ending index is going to be three plus three, which is six. And so I would actually
16539.969 -> slice starting at this character, three, going all the way through the end, right? 345. And
16545.818 -> I always exclude the ending index of six here. So this logic is good, right? All it does
16550.539 -> is check if my current word matches the characters starting at index i, goal. So if I enter this
16557.09 -> if statement, then I have a successful word. So I should actually set a true in that forward
16562.469 -> looking position. So I can just set table. And when I look ahead, how far do I need to
16568.488 -> look? Well, you need to look exactly this far ahead, right, i plus word dot length,
16573.26 -> matching me we're at index zero of our target string. If I take ABC, then I end up word
16579.41 -> dot length characters later, three characters later in this example, right. So I'm saying
16583.488 -> table of i plus word dot length, that should be set to true. Cool, notice how similar This
16589.709 -> is to our previous number, like our target some problems, except now we have to adapt
16594.641 -> this four string data. So now I jump ahead based on the length of the word I'm choosing.
16599.609 -> So this code is looking pretty good. Let's go ahead and do our final return value. So
16606.139 -> I want to return the table at Target dot length, right? Basically, if I'm at the last position,
16613.77 -> or at Target length, a position of my table, that means I have the information for the
16618.92 -> entire target range starting at index zero all the way through the end of the target.
16625.051 -> So here are some examples. Let's give this a run. So I get true, false, true, false.
16631.32 -> Nice, and there we have a nice working solution. So take a moment to let this code sink. And
16637.469 -> really the most tedious logic here is probably this particular slice. So really try to make
16644.581 -> sure you kind of understand how this comment is implemented using this code. If you remember
16651.25 -> our previous recursive immobilization strategy for this problem, we needed to actually have
16655.879 -> logic to make sure our prefixes match. This is sort of the analog for that, right. So
16660.869 -> we already spoke about the time complexity for this one, but it's already in plain sight,
16664.75 -> right, we have m iterations here, we have n iterations here. And then the additional
16669.551 -> work we do is this slice, right, I'm slicing from my target, which is also M. So overall,
16674.139 -> I have m times n times m, or m squared, N. And of course, the table just takes up definitely
16680.939 -> m space. Cool. So with that, let's head back into the drawing board. All right, now let's
16688.949 -> revisit the count construct problem. So we're still going to take in a target string as
16692.68 -> well as an array of words in a word bank. And this time, we want to return a number,
16695.479 -> right, we want to turn the number of ways that the target can be created by concatenating
16699.98 -> elements of that word bank, we can reuse elements of the word bank as many times as we want.
16705.67 -> So let's take a look at an example of this. So let's say I had this input, so I have the
16709.279 -> target of purple, and I have an array with some substrings. The return value here should
16713.578 -> be two because they are exactly two ways to create purple. You can do perp plus le, or
16718.6 -> you can do p plus u r plus p plus le ran, those are two distinct ways. So notice in
16723.891 -> this problem, we really just want to return to the numbers. So we'll just keep that in
16726.939 -> mind. So let's follow that classic tabulation recipe, we'll initialize our table with roughly
16732.479 -> the size of the input that changes, which is really going to be the target string, right,
16736.4 -> we want to actually use overlapping subproblems on the target string.
16741.969 -> And like we always say, we're going to have a little off by one scenario here. Although
16744.939 -> the number of characters in my purple string is six, I want to initialize a table of length
16750.488 -> seven. In general, you want to make sure your table has target dot length plus one a number
16755.619 -> of elements inside. That way, I have a way to represent the empty string, just like our
16760.119 -> last tabulation. And so we'll start by thinking about what are some proper seed values for
16765.818 -> this problem. I know that when I have an empty string, empty target strings, my input, no
16771.09 -> matter what array I'm given, you can always generate the empty string right by taking
16774.988 -> no elements of the word bank. So the answer for the target string should be one, right,
16778.888 -> there's one way to make the target string and that is to concatenate nothing together.
16783.158 -> So I'm going to store the value of one at index zero of my table. Now I have to decide
16788.658 -> how to initialize the other values in my table. Well, I know that it should be number data,
16793.2 -> right? It's really important that you try your best to make your table contain a consistent
16797.128 -> data type. If I remember my similar counting problems like Fibonacci, or even the grid
16802.638 -> traveller of the move here is actually start those other spots in the array at zero, basically
16808.18 -> saying, you know, at the start, I have zero ways to generate these different prefixes
16812.61 -> of purple. And then as to actually find distinct ways, I'll go ahead and increment those spots
16817.87 -> in the table. But we'll get to that. So we'll iterate through our table from left to right,
16822.54 -> of course, beginning at index zero of the table. And as we do this, I'll also up top
16828.01 -> show the characters that we're considering within our table. Remember, the really important
16833.91 -> pattern here is when I'm at some position of my table, that means I'm encoding some
16839.488 -> information about the substring that starts index zero, up to but not including my current
16845.42 -> index, right. So if I have some information stored at index zero, that information is
16849.6 -> about the empty string. From my current position. Now I need to consider any words of the word
16854.041 -> bank that I can take right now to give me another spot within the table. In other words,
16859.29 -> since I'm at index zero, I need words that start with P. So let's say we looked at this
16863.25 -> first word of perp. And so if I looked at that word, and I took it, that would bring
16867.53 -> me a somewhere else within the table, right have to look forward like we usually do. So
16872.29 -> they'll end up at this position index four. Again, notice that we have the kind of off
16876.87 -> by one scenario here. So although I'm going to write information into index four, that
16882.79 -> informations about the substring, that spans from zero, up through index three, right,
16887.02 -> and what I need to do now is take my current value in yellow, actually add that into my
16892.458 -> future position. So I just add one into the zero. And now index four contains a one, right
16898.27 -> basically saying that so far, we found a one way to construct the string perp. And then
16904.798 -> from there, I look at any other words that the word bank that match right, so I'll just
16908.25 -> look at the next word of P. Again, just moving left to right through our word bank, right
16911.648 -> now, I'm still at my current position. And what I do if I use this P is I only look one
16916.378 -> character ahead, right, because p dot length is only one. What I do in this future position
16922.45 -> is of course, just take my current value in yellow, and I added into this position, so
16928.478 -> zero plus one is just a one. There is one more word I can use right now from my current
16934.228 -> position, that's purple without the E. And if I look at that corresponding position,
16938.458 -> they'll bring me all the way over here. And again, I add the one into that position. Nice,
16943.75 -> I think that ends are a very first pass. And now we move our current position to the right.
16949.798 -> So now my current is at index one. So now I need to consider words of the word bank
16953.67 -> that begin with a you and kind of match characters throughout. The only option here is really
16958.078 -> just you are in the word bank, if I took a u r, that would bring me two characters ahead,
16963.17 -> right, so bring me over here, what I should do like always, is just take my current value
16967.808 -> and add it into that position. Now I have a one over here. There are actually no other
16972.398 -> words of the word bank that match from my current location. So I can just move ahead
16976.52 -> to the next spot in the table. If we're at index two, it looks like we need something
16981.798 -> that starts with an R, there's actually no words of the word bank that we can use from
16985.398 -> this position. And what we also notice is there's also a zero at this opposition. So
16990.068 -> this won't really do anything in our table anyway. So we can progress just the next spot
16993.75 -> in the table. Here's where things start to get interesting, right? So right now my current
16999.02 -> is at index three. So I need to grab words from the word bank that start with P and potentially
17005.138 -> have an L enemy. So what I can do here is just take this loan p, which will bring me
17011.068 -> one character ahead. If I look one character ahead, what I do now is I take my current
17016.898 -> value, right circled in yellow, and I add it into my look at value. So I increment that
17022.908 -> to two now. Nice. And then from there, I need to continue on my logic by just moving rightward
17028.78 -> in my table, right shift my current point of view.
17033.34 -> At this point, what I do is I again, look at any words that start with an le, and there's
17037.658 -> really only one that matches perfectly, I look two characters head because the length
17041.59 -> of Le is two. And what I do is I take my current value in yellow, and add it into that position,
17046.808 -> right, my logic throughout this entire algorithm is very consistent. And there we have a two
17051.29 -> in the last spot, you can probably already foresee that we have our final answer. But
17055.138 -> if we continue the algorithm as normal, or really, we've noticed that if we move to the
17058.77 -> next position, there's no word of the word that starts with E. So I don't do anything
17062.87 -> here. And once we hit the very end, we're actually done with our algorithm. Right? And
17067.92 -> we see that, hey, we have a two in the last spot a retailer which is consistent with our
17072.37 -> expected answer. Something I think is really important to do is not only recognize that
17077.26 -> our final answer is in the last position of the table, but really any element of this
17082.718 -> table should make some logical statement about our target string, right? So let's say I look
17087.67 -> at index for over here. I see that there's a two in that spot in the array. So what does
17093.11 -> that really saying? Well, if I am looking at the value stored at index four, that means
17098.84 -> I'm considering the substring That starts at zero and goes up to including four. So
17103.208 -> that really means I'm looking at purp, right. And if there's a two inside of the spot in
17108.53 -> the array, that means that there are apparently two ways to create perp. And if I do a quick
17113.068 -> check in my word bank, that's exactly true, right, you can just take the straight up perk,
17116.43 -> because that's actually a literal word of your word bank. Or you can do p plus u r plus
17121.19 -> P. Cool. That's some consistent information. And then from there, if I actually consider
17127.048 -> now what I can do from that position at index four, well, you could have taken an le, that's
17132.45 -> basically what we did when we traced through the algorithm. If you take an le, what are
17137.34 -> you really doing? Why is that value in the index six, also two? Well, for one, Ellie
17142.958 -> has a length of two. So that's why I'm looking just two spaces ahead in my table. And if
17148.208 -> I think about what like logical operation I'm doing, if I were to take an L e, and add
17153.558 -> it to all of the ways that I have in blue Rhino, then I would end up with all of the
17158.34 -> ways to generate purple right in my last spot, literally just taking the Le as an additional
17164.62 -> move, right? Pretty elegant logic. Let's talk about the time complexity for this though.
17170.53 -> So we know that, hey, r m is going to be the target and n is going to be the number of
17174.25 -> words in the word bank, this is really going to be about the same complexity as our last
17178.818 -> rendition, right? You can't construct. So we're gonna have m squared times n time, right,
17184.27 -> we definitely have m time for just iterating through every spot of the array. And we also
17190.218 -> have an additional n, because we have to actually look forward for every element of the word
17195.389 -> bank. And so where does that final m come from? Like we've been saying lately, it comes
17199.18 -> from actually performing the match of the check for matching characters. In a similar
17204.74 -> way, the space complexity is just going to be m, really just based off of the size of
17209.52 -> the table, we're only ever storing just some numbers within this table. So our space just
17215.11 -> stays at linear o of M space. All right, I'm feeling good about this plan, time to code
17220.298 -> it up. Alright, let's code this one out. Let's start by creating my table, it's gonna begin
17227.638 -> as an array with roughly target length, size, really target length plus one, that way the
17234.158 -> index aligns, right, I'm going to fill this entire table with zeros for now, because I
17238.87 -> know in the long run, I'm going to add into it. So zero is a great starting value. That
17243.128 -> being said, I have a really important c value, I need to make sure that my table at index
17247.978 -> zero actually starts as one, right, because there is one way to make the empty string
17253.29 -> right. Now from here, I can start my core algorithm by just iterating through every
17257.69 -> position of the table. So set i equal to zero, go up to an including target dot length, and
17265.58 -> hit every index, right. And so what I'll do is iterate through every word of the word
17270.61 -> bank. So let's word of word bank.
17275.138 -> And as I iterate through every word, I need to check if the word matches my current location
17281.01 -> right there has aligning characters. So you've seen this pattern before, I'll check if target
17286.1 -> that slice I'm gonna slice some characters out of my target. Starting index i right,
17290.298 -> this is my current position, I need to look at the next couple of characters based on
17294.708 -> the length of my word, I'm going to slice i to i plus word dot length. goal. So my current
17303.34 -> position is I, this will give me the next word dot length number of characters starting
17308.44 -> at position, I will check if that little segment is equal to the word I'm considering right
17313.8 -> now. And if it is equal, then I have a word that I can actually take, I can modify some
17318.658 -> further position of the table. In particular, what I should do is need to modify my table
17325.53 -> at index i plus word dot length. So I'm looking to head remember that our current position
17330.6 -> is I so I'm looking ahead in the table based on my current words length, what I want to
17335.01 -> do is actually increase that by some value, want to increase it by the number stored in
17341.171 -> my current position, right? That's what we did at every point of the algorithm. Nice.
17346.18 -> A common mistake that I see students make is, usually they have a tendency to just increment
17350.77 -> by one here, right? That's not the move Do you want to do is increment by exactly the
17356.128 -> number that's in your current position, right. Now, at this point, I think all we need to
17361.23 -> do is just return our final answer, which like we always know, is basically table at
17366.01 -> Target dot length. Cool, so I have some examples below. With their expected results, let's
17373.33 -> give this a shot count construct. So I get to 104 and zero, that's that we have a fairly
17379.828 -> large example, which should return zero, you can't possibly generate this final f here,
17385.148 -> because your word bank only contains a bunch of E's, and it ran fairly quick. So this solution
17390.158 -> is looking pretty good. Notice how small of a variation it is from our last problem. Hopefully
17396.28 -> you're really feeling the progression. So let's kind of quickly just take a look at
17400.6 -> That that was our can construct problem. So let me split these guys. Very, very trivial
17409.058 -> difference, right, instead of assigning a true value. Now I actually just increment
17414.62 -> because I have numbers within my table. That was short and sweet. Let's head back into
17420.048 -> the drawing board. Let's do one more problem together, let's revisit that all construct
17427.219 -> problem. So we're still taking in our target string, as well as an array of words in a
17430.6 -> word bank. But this time, you want to return all of the ways that you could construct the
17434.39 -> target by concatenating elements of the word bank. So here, you need to return a 2d array,
17439.828 -> that means a single element of the array represents a one combination that makes it a target.
17444.238 -> And like usual, we can reuse elements of the word bank as many times as we see fit. So
17448.76 -> we take a look at an example, let's say I gave you the target of abcdef, I gave you
17453.53 -> this array of words, you actually have many ways to create the target string, there are
17458.691 -> four ways over here. So each sub array represents one combination, where if you concatenate
17464.45 -> it together, you get the target. And so notice that I want to return every single combination
17470.2 -> as an element of the outer array over here, right. So if there are many ways to create
17474.648 -> something, then I want to return a 2d array. That being said, there are a few examples
17479.78 -> that we should keep really in mind having to do with our seed values that we kind of
17483.12 -> foresee. Right. So let's say I gave you this example, where your target is the empty string,
17487.11 -> and I gave you just some words in the word bank. The point is, since your target string
17491.66 -> is empty, your result should really be a 2d, empty array. So recall that the outer array
17498.568 -> here represents like the collection of multiple combinations. And here, there's only one combination
17504.26 -> that makes the empty string, right, so the inner array represents like that empty combination,
17509.27 -> we take no words of the word bank. And this is a reasonable thing to do, because they'll
17514.24 -> take give you this example, if your target was birds, and I gave you an array of cat
17518.19 -> and dog, then the result, there should be just a one dimensional empty array, right?
17523.77 -> The array over here represents the collection of combinations. And there are no elements
17528.69 -> here, right? There are no combinations at every yield of birds. So we'll keep those
17533.51 -> sort of seed value base case scenarios in mind. So let's trace through this example
17539.84 -> of abcdef. I'll start by initializing our table, we're gonna need a lot of room in this
17545.04 -> one. And so we know that the size of this table is going to be based on the target string,
17549.708 -> right, just like we usually do. And then we have to figure out what our seed values are,
17553.978 -> we kind of just spoke about them, we know that if I have the empty string, then I need
17558.5 -> to have a 2d empty array. And so we'll initialize that in our table,
17564.45 -> I know we're going to need a lot of room. So I'm going to spread out these outer brackets.
17568.76 -> Now for all of the other elements of our table, we want to make them just one dimensional
17573.818 -> empty arrays. And here's the reason why I write for these further values in the table,
17580.308 -> I have to actually check for ways to generate them. So for now, there are no ways that I
17585.738 -> found right, that's why I just have an array with zero combinations inside, except for
17591.35 -> index zero, right? index zero has one combination inside, that happens to be the empty combination.
17596.488 -> Cool. So let's start with our general algorithm. A lot of this logic will feel very similar
17602.1 -> to our last few problems. Now we're just adjusting it have to generate these combinations, right.
17608.128 -> So we know that we're going to try to visualize how we look at our string in terms of some
17612.808 -> prefixes, right. I know that if I'm highlighting some spot in my table, then that grabs the
17617.6 -> prefix or an index zero all the way up to midnight, including my current index of the
17622.048 -> table. And I'll light up those characters at the top over here. So what I need to do
17626.579 -> now is since I'm currently at index zero of my table, I consider words of my word bank
17631.128 -> that have matching characters from this position. So I can look at this a B First, if I took
17636.808 -> this A B move, then I should look two characters ahead, right, because a B has a length of
17641.02 -> two. So that brings me to this position in light blue. And what I should do is take all
17645.44 -> of the combinations in my current location in yellow, and actually copy them over into
17650.888 -> this future position. But when I do that, I also need to be sure to include in those
17656.61 -> combinations, the move of the word I just took, right, so that would just be adding
17661.1 -> an AB inside. Right. So again, kind of repeating that step, I copy over the contents of my
17667.828 -> current position in yellow. But then I have to make sure I add my word that I'm consuming
17672.7 -> right now into each of those combinations I just added. Cool. And now we would actually
17678.798 -> keep our current position here because there are still some other words of the word bank
17682.558 -> that have matching characters, right, I can take an ABC from this position, and something
17687.578 -> very similar happens write a copy over my current array. And then I go ahead and add
17692.968 -> ABC to it. This happens one more time for ABCD
17696.718 -> Awesome. At this point, I've considered all of the words that match from this current
17706.62 -> position. So I can move forward a little bit. At this point, though, I need something that
17711.478 -> starts with A, B, right? Because my current position has to be on it. And I actually don't
17717.62 -> find any words of the word bank, that kind of match from this position. So I actually
17722.408 -> moved my current once more. What about this position, I have to consider words of the
17727.62 -> word bank that have a seat, right, and there are a few that can use here, I can use CD,
17731.78 -> right. And if I do that, I look two characters ahead or two indices ahead for my current
17737.27 -> position. So I'm looking at this index. So if I look at index four, I have a really interesting
17742.11 -> situation, right? I already have a some combination stored inside of index four. And that represents
17748.1 -> one way we could make ABCD, right by just taking that loanword ABCD. But what I can
17753.78 -> also do is, I should take all of the combinations from my current position and just add them
17758.818 -> into that index for right, so I'm taking the A B from index two, just adding it to index
17764.158 -> four. But then I also need to add to that new combination of the word I'm using right
17769.83 -> now, which was CD. Cool. And then from here, you already noticed that, hey, there are so
17775.81 -> far, two ways to create ABCD, you can just take that loan word ABCD. Or you can do a
17780.388 -> B plus CD. So this logic is taking some some nice shape here. For my current position,
17785.59 -> there's actually one more word that matches, it's really just a single character see, so
17789.67 -> like I do, I look one character head or one index head in my table just next door, and
17794.16 -> I copy over the combinations from my current location in yellow. But then I also append
17800.25 -> the word I'm consuming, which is just an additional C, right. So so far, I have two ways to make
17805.76 -> ABC. At this point, I can progress my current spot and table. And I look for any matching
17813.31 -> words that start with a D, right? So that would be the D f over here. d f has a length
17818.048 -> of three. So I look three spots ahead in my table. And I need to add some information
17822.298 -> here, I copy over my current combinations in yellow. And then I have to append at the
17828.128 -> end of all of them the D F word that I'm using right now. So it looks like this. At this
17835.02 -> point, I can iterate my current position. So now I'm at index four as my current. And
17839.708 -> I look at any words that start with any, and there's actually just one over here just as
17843.25 -> EF. And so I look to character's head or to indices ahead for my current position. And
17848.44 -> again, this is a scenario where I need to append new combinations to this existing list,
17853.27 -> right. So if I look at my current position at index four in yellow, I'm going to take
17857.84 -> all of those combinations and copy them over into my light blue position at index six.
17863.298 -> And then from there, I also need to make sure that I append to them the word I'm using right
17868.52 -> now, which is an E, F. So those two combinations also get an F at the end of them. Nice. At
17874.5 -> this point, you see our final results at the last spot on the table, we continue the general
17879.238 -> algorithm, we would just move our current position to index five, which is actually
17883.59 -> a no words of x star with an F over here. So nothing happens on this iteration. And
17889.86 -> there we have our algorithm, right, that index six of the table contains all of those four
17894.09 -> ways we described how we can use to create our original target string, take a moment
17899.26 -> to look at this table. And notice how every position of the table should make some logical
17904.658 -> statement about the target string. Right, I can see that if I chose some position, let's
17911.048 -> say index three, that's giving me some information about the substring ABC, right, that prefix
17917.37 -> of my original target. And what saying is, there are two ways to make ABC, you can just
17921.86 -> take ABC, that's actually a word of my word bank. Or you can do a B plus C, right? That's
17926.94 -> the only other way that applies for all positions within my table. If I look somewhere else,
17933.62 -> like at index five, that means I'm reading some information about A through E. And if
17939.45 -> there's no combinations at this position, means there's no way you can ever generate
17943.638 -> ABCDE. And if you take a quick glance at the words in the word bank, that is correct, you
17948.908 -> can't possibly just generate ABCD. Overall, the information in our table and our logic
17954.728 -> that we did was very consistent. Before we hop into the code for this one, let's do the
17959.51 -> analysis. So we know that m is going to be the length of our target string. And we'll
17964.129 -> define n to be the number of words in the word bank. So like we described, when we did
17968.708 -> the memorization and recursive solution for this problem, we really can't do better than
17973.92 -> a sort of brute force, right? Because they're asking us to return literally every single
17979.208 -> way to generate our target string here. So really, the time complexity is going to be
17985.4 -> the shape of an exponential, right, so about n to the M, again, it will have some extra
17990.808 -> like multiplication terms of M
17992.648 -> in it. But overall, we're looking at an exponential time complexity. And what's the reason behind
17998.048 -> that? Well, we know there's going to be an exponent The number of actual combinations
18002.958 -> that we need to return. And I have to actually construct all of those combinations piece
18007.628 -> by piece. And so I'm looking at at least exponential time. And really, once something gets exponential,
18013.468 -> that's really the limiting factor, right. And similarly, we can say that the space complexity
18019.18 -> for this is also going to be exponential. Again, roughly, we know that we're going to
18023.18 -> have at least m space just from the straightforward sides of the table. But notice that every
18029.01 -> element of the table itself is going to be a 2d array. So in a sense, we're really dealing
18035.25 -> with like a 3d table in this instance. And so we know that at any particular position
18041.658 -> of our table, we're going to have potentially an exponential number of things, right, if
18046.95 -> you consider the very last spot in our table, just the last spot that already has an exponential
18052.708 -> number of combinations inside of it, but then I would actually have that throughout my entire
18057.73 -> table, so it'd be really a little more than just n to the M. But overall, I'll say it's
18063.34 -> roughly exponential complexity. And you can't really do any better here, right, especially
18067.68 -> in terms of the time, I'm really asking you to just do this in a brute force way, I think
18072.77 -> we'll actually be able to see that once we create the solution. Let's hop into it. Alright,
18079.078 -> for one last time, let's solve this all construct problem. So I'll begin by creating my table.
18083.578 -> By now we're tabulation masters, this is very, very formulaic. Create my table with roughly
18090.11 -> target size really target dot length plus one. And here, I need to initialize the elements
18096.16 -> of my table as arrays. So here, I need to be very particular and make sure I get a unique
18100.648 -> array in every spot of the array. So I'm going to do a fill using this index before, then
18105.98 -> I'll map over it that way I can generate a new array for every element here. Cool, let
18110.79 -> me just style this up. And so this should give me an array as every element of my table,
18115.84 -> right? Notice that the elements right now are just one dimensional empty arrays, right?
18120.95 -> These arrays represent the collection of combinations. At the start, they're empty, meaning that
18125.29 -> I have no combinations for most of these elements, except for one, right, we already spoke about
18131.158 -> table at index zero, right? That should be a 2d, empty array. And the reason was, still,
18138.19 -> the outer array here represents the collection of combinations. For the empty string, there
18142.85 -> is one combination that you can use to make it right, that is the empty combination, where
18146.59 -> you take no words to the word bank. Cool. So now I can begin my general algorithm by
18151.28 -> just iterating through my target, and you've done this many times before. So I'm iterating
18155.35 -> through my table. And what I'll also do is look at all of the words, right, so in every
18160.7 -> iteration, I'm going to consider some words of the word bank. So I'll say that word of
18164.93 -> word bank. And like before I send you to check that, alright, these words better have matching
18171.6 -> characters based on my current position. So I'll use that classic logic, right, seen it
18175.468 -> many times by now. And I'll slice a segment out of my target string, I'll say target dot
18180.65 -> slice starting index i, and grabbing the next word, dot length, number of characters. Cool.
18188.67 -> I'm going to compare that chunk to the current word that I'm iterating through. Nice. So
18194.058 -> using this statement, I'm sort of filtering down all the words, and only going to do some
18198.56 -> logic on the words that match based on my current position. Nice. And so we've seen
18204.318 -> you know, this nested for loop an if statement before, right, we're now we're adding our
18208.378 -> new logic within this if statement. Cool, all I need to do is really understand what
18214.02 -> types of data I'm dealing with here. All right, so let's say I just wrote this expression
18218.92 -> table at index i, well, that's going to do is just reference my current spot in the table.
18224.52 -> And if I think about what data it's going to be, it's definitely gonna be an array,
18227.548 -> right? It's either going to be like an empty array, meaning there are no ways to make this
18232.47 -> current target. Or it could be a multi dimensional array, where every sub array represents one
18237.42 -> way I can make the current target. So if this is all of the combinations that generate my
18242.148 -> current, then what I'll do is I'm going to map over them. Remember, in our drawing, what
18247.558 -> we did was kind of copy over all of our current combinations. But then make sure we also add
18253.72 -> our word that we're using right now to the end of each of those combinations. So that's
18257.37 -> what I'm doing right here, and a map over every summary. And what I'll do is, I'll just
18265.058 -> copy over the elements of that summary using that spread syntax that we've seen before.
18269.378 -> And then also be sure to add on at the end my word. Nice. And then that will give me
18275.86 -> let's say, our new combinations. So I'll just say that to a variable maybe. So new combinations
18282.02 -> is going to be a 2d array, right where every sub array represents some combination.
18286.138 -> And I want to take these combinations and add them into that further spots inside of
18292.43 -> my table. So I know I need to kind of look at in my table. So I can look at table at
18297.078 -> index i plus word dot length. I know that all positions of the table are going to be
18302.878 -> arrays, right? So this is going to be an array, there's going to be some data that might already
18306.558 -> exist at this position, right? So what I don't want to do is just assign you combinations,
18312.19 -> because then that would overwrite any previous existing combinations that I already found
18317.17 -> for this position. Instead, I need to make sure that I just add these new combinations
18322.458 -> to that list, right. So what I'll do here is I'll push to that array, I'll push all
18327.558 -> of the elements of my new combinations right here, I can spread this out, I want to spread
18332.1 -> it out. That way, I don't add any additional nesting over here, right. So this is looking
18336.85 -> pretty good. I think let's go ahead and return the table at Target dot length. Here, I have
18345.99 -> some examples down below. Let's give this a shot. Nice. And these answers look correct.
18353.808 -> Right, notice that these last two examples should return empty arrays because they are
18358.138 -> impossible to generate. Cool, let's hover over this code for a moment. Probably the
18363.878 -> most tedious logic here would be these two lines, right? Really try to understand how
18369.78 -> like these two lines translate into the logic we did in our drawing. So let's break down
18374.898 -> these lines again, right? line 11 is going to take all of the combinations in my current
18381.05 -> position in my drawing that was like my yellow box. And it's going to add our current word
18386.218 -> to each of those combinations. And then we're going to take each of those new arrays and
18391.888 -> add them to the list add our further position, right being sure not to overwrite, we already
18398.25 -> spoke about the complexity of this one, it's definitely going to be exponential, probably
18401.908 -> a little more than exponential. Right? If you imagine all the work we have to do using
18406.39 -> these maps and like spreading, we have to actually construct our result array, which
18412.35 -> is going to contain an exponential number of things. Right? So when I look at this last
18417.62 -> example, let's say that I made it a little longer, right? So let's say actually pull
18422.92 -> up the size of that input. It's going to be very, very slow might even crash, but let's
18428.5 -> just do it. So let's say I had a bunch of A's here, just for fun. So if we try this
18434.48 -> example. Yeah, it looks like our program still crashes, right? This is going to return a
18441.908 -> really, really a massive array probably can't even fit into memory over here, right? So
18447.068 -> we'll get like a stack size exceeded, even though this isn't even recursive, right? Just
18451.51 -> calling functions way too much over here. So no matter what we do, we really expect
18457.628 -> this problem to sort of demand an exponential solution. With that, let's head back into
18462.708 -> the drawing board where you can wrap up this course.
18465 -> Alright, programmers, by now we solve many different dynamic programming problems together.
18468.728 -> And I want to leave us with some final advice. So whenever you're starting to attack any
18472.978 -> dynamic programming problem, start by really taking a moment and noticing any overlapping
18477.68 -> subproblems. And then from there really focus on like the input to the problem, in particular
18483.47 -> its type. And from there, you shouldn't be able to recognize what is the trivial smallest
18487.408 -> input? So in our string problems, usually that means the empty string in our array problems,
18492.298 -> it's the empty array, or in our number problems, it's usually a number like zero or one, right?
18497.26 -> Is there some input wherever you don't really have to do any extra work to know the answer?
18501.86 -> The answer just sort of is what it is. Once you recognize this trivial scenario, or the
18506.628 -> base case, really, then you'll want to realize how you can relate this base case toward your
18512.02 -> larger inputs. And then you have two options, right? You can either think recursively, and
18516.61 -> use memorization, or you can think more iteratively and build a table using our tabulation formula.
18522.878 -> I think as you practice dynamic programming problems, I would actually always solve problems
18527.35 -> in two ways, one using memoization and one using tabulation. And of course, from that
18532 -> point on, it's okay, if you have like a favorite strategy. For me, personally, that's memorization.
18537.01 -> But that being said, it really helps to have options when you're in an interview. And once
18541.068 -> you've made that decision on, you know which route you're going to take, definitely, you
18544.468 -> know, slow it down and draw a strategy first. This is I think, the most important thing
18549.298 -> about just data structures and algorithms in general. Usually, when we draw our pseudocode,
18554.308 -> or draw our process on the whiteboard, we can recognize all of these edge scenarios.
18559.28 -> And it's much harder to do when we just write like the code out of the gate. And so I promise
18564.718 -> if you slow it down and take a moment to draw a visual, you're gonna find your work a lot
18569.01 -> more productive, as well as more intuitive when you explain it to someone else. And of
18573.56 -> course, through all of that, you're gonna have a lot more fun, you know, dealing with
18576.068 -> these quite hard topics. Alright, programmers, that's all I got for this course. Hopefully,
18581.12 -> you had fun, you know, learning this new topic. I definitely had fun teaching it to all of
18584.86 -> you. So what you want to do now is head down to the links in description and head to coder
18589.24 -> byte comm where you can actually practice this new topic. Thanks for watching, and I'll
18594.04 -> see you in the next one.

Source: https://www.youtube.com/watch?v=oBt53YbR9Kk