Dynamic Programming - Learn to Solve Algorithmic Problems & Coding Challenges
Aug 15, 2023
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