YouTube2Text

Recursion in Programming - Full Course — Transcript

by freeCodeCamp.org · 19,459 words · 1,099 segments · language en · Watch on YouTube

Full transcript

  1. 0:00When learning about recursion, it can seem  like you're always going back to the beginning.
  2. 0:04In this course, the simple engineer will help  you understand recursion using animations,
  3. 0:09thought processes, and more. Hey, guys, and  welcome to another video brought to you by
  4. 0:14the simple engineer. In today's video, we are  going to delve deep into the depths of recursion,
  5. 0:19and strengthen your algorithmic mental model  around this programming paradigm will do so by
  6. 0:26looking at a variety of different examples  and animations. So let's get right into it.
  7. 0:41The first question that we have to answer about  recursion is what even is recursion? And I think
  8. 0:47the best way to talk about it is through this  analogy, and let's imagine that you're sitting in
  9. 0:52line waiting to withdraw money out of an ATM. And  for the sake of this example, let's assume that
  10. 0:58this right here is you and you have this question  this underlying question. And you want to know
  11. 1:03how many people are standing in front of you in  this line. And this kind of brute force, iterative
  12. 1:10version of you says, Well, what I can do is I  could step out of line, I could run to the front,
  13. 1:15and I can maybe, maybe count one by one. And maybe  I store these counts in some auxilary, you know,
  14. 1:22notebook that I have some external variable that  you can imagine, and then you run down this line,
  15. 1:28and you get back and you say, Okay, I got my  answer. But I did a lot of work. And we want
  16. 1:32to kind of switch this paradigm, we want to think,  how can I be lazy? What's the least amount of work
  17. 1:38that I can do, and sort of break this problem down  in some sort of sub structure? And so what I do
  18. 1:43well, I tap on this girl's shoulder in front of  me, and I asked her a very simple question, I say,
  19. 1:48hey, what number are you? And she turns around  and looks at me and says, you know, I'm sorry,
  20. 1:51I don't really know. But what I'll do is I'll ask  the woman in front of me. And what happens is,
  21. 1:57this process kind of continues, and it continues  up until we hit some stopping condition. And this
  22. 2:04condition that we get stopped at is when we hit  this woman, and she taps on this guy's shoulder.
  23. 2:08And he says, she says, hey, what number are you in  line? And he gives this response. And he's number
  24. 2:15one. And this is interesting, right? Because this  is kind of the stopping condition to this sort of
  25. 2:20problem that we were solving. And what she does,  if she takes that number, and she says, Well,
  26. 2:26if he was number one, then I'm basically  one plus one, because I count myself.
  27. 2:33And this guy says, Well, if she was number  two, that I'm one plus whatever she was,
  28. 2:38and this idea unravels backwards to the original  asker. And this woman basically says to the guy,
  29. 2:46hey, there were, you know, 10 people  in front of me, I count myself.
  30. 2:50And now I'm at 11. And it turns out, that we can  model this problem with this simple blueprint,
  31. 3:00we ask the first question, what's the least  amount of work that I can do? How do I break
  32. 3:05this problem down into some sub problem?  And the second question that I have is,
  33. 3:09when would the process complete? Like, what's  my stopping condition. And in this case,
  34. 3:13it's when we hit the first person in line, which  is the gentleman withdrawing money from the ATM,
  35. 3:21it just so happens that we can actually write  this problem in a very simplified, minimal,
  36. 3:27beautiful piece of code. And the function that  we have is just this get my position in line. And
  37. 3:33we have this kind of abstraction of a person  that we have. And notice the return values in
  38. 3:38integer. And, again, if we think back to those  two questions that we have for this blueprint,
  39. 3:45we say if the next person in line is null,  then we must be the first person in line.
  40. 3:50Right? That's kind of this base case, imagine  nobody is in line, well, then you're probably
  41. 3:54the first person in line. Now, if we don't satisfy  this conditional, then what do we do, we do just
  42. 4:01a little bit of work, we say, I'm going to count  myself as somebody that contributes to the number
  43. 4:06of people in line. And I'm going to add this  kind of recursive call, and I'm calling myself.
  44. 4:12And this is the interesting property  of recursion, we're calling our self.
  45. 4:16But the parameter that we condition on actually  further progresses us to the problem that we're
  46. 4:22trying to solve. And so I'm not I'm not passing  the same person object into the function again,
  47. 4:28I'm actually passing a person that progresses me a  little bit closer to the question that I'm asking,
  48. 4:34which is, how many people are in front of me. And  this is really all recursion is about is how can
  49. 4:39I take some large problem and break it down into  a bunch of subproblems such that each invocation
  50. 4:45of my method gets me a little bit closer to the  problem that I'm trying to solve? Let's think
  51. 4:50about another example. Let's take back to the the  school days where you're writing a bunch of essays
  52. 4:55and typically this process, at least for me, is  you know, you writing So you submitted to your
  53. 5:00professor and he says, Hey, you know, that essay  was terrible, I want you to go make revisions.
  54. 5:06And the essay gets passed back to the student.  And this process can actually continue over and
  55. 5:10over and over again, you write an essay, you  make revisions, you submit it, it gets denied,
  56. 5:14and you rinse and repeat. And you do this  until the professor says, Okay, that was good,
  57. 5:19I'm going to put it into my briefcase, and start  grading it. And it just so happens, this, again,
  58. 5:26is this kind of recursive strategy that can be  developed. If we look at a simple piece of code.
  59. 5:33And what are we doing, we're really, we look  at an essay, we revise the essay, we read it,
  60. 5:38we get feedback on it, we apply changes to it.  And then notice, we just call ourselves again,
  61. 5:44right. And there's a single base case  kind of hidden in here. And we do it
  62. 5:49until the essay is complete. And notice  that each invocation is the same essay
  63. 5:54object. But what we've done is we've inched  our way a little bit closer to that goal,
  64. 5:59that goal of no longer needing revisions  on the essay. So the whole process again,
  65. 6:05just to re emphasize is that we do a little bit of  work on each invocation of our method call Intel,
  66. 6:13we hit some base case, some stopping condition  that says, hey, you no longer need to continue.
  67. 6:22So again, like what is recursion, recursion is  nothing more than a method that calls itself
  68. 6:29and to be more precise, it's usually a method  that maybe returns a value, maybe it doesn't.
  69. 6:36But it's conditioned on some parameter, such that  when you hit some conditional at some point in
  70. 6:42time, you can actually stop recursing, some base  case, right. And we consider this piece the base
  71. 6:48case, this is the stopping condition, such that  we no longer grow the number of recursive calls
  72. 6:54that we're storing in memory. And this piece down  here, this is the recursive call. This is where I,
  73. 7:00you know, I do some unit of work, some small sub  problem that inches me or progresses me closer
  74. 7:07to the goal or question I'm trying to  answer. Before we dive into the technical
  75. 7:13intricacies of recursion, it's important  that we discuss some of the trade offs.
  76. 7:18Why would we want to use recursion? And  why would we want to avoid recursion.
  77. 7:23And I think there are valid examples for both.  And it really comes down to the situation.
  78. 7:29The first pro that I'll give is that it really  bridges the gap between elegance and complexity,
  79. 7:35we will look at a variety of different  problems where we're traversing
  80. 7:38complex data structures like trees and graphs.  And it really boils down to three or four lines of
  81. 7:44code. And that is vastly different than doing  something like that, in this kind of imperative
  82. 7:50approach, where we're, we're looking at things  with a lot of loops and a lot of variables. And
  83. 7:54that can get really messy really quickly, with  data structures that are inherently recursive.
  84. 8:01Now, on the downside, you know, adding a bunch of
  85. 8:05method calls on the call stack incurs some  CPU overhead, right, there is some slowness,
  86. 8:10with calling methods in your code compared  to iterating through a loop. And that, again,
  87. 8:17is another trade off you need to make it can be a  time or space trade off that you need to consider.
  88. 8:25Now, I touched on this briefly, but again, like  recursion can reduce the need for complex loops,
  89. 8:32and auxiliary data structures. recursion has  this sort of implicit stack, which is a data
  90. 8:38structure commonly used in a lot of algorithms.  And so having that sort of implicit stack and
  91. 8:44kind of self manage looping construct, it's  given to you as a part of recursive calls,
  92. 8:50you can exploit that property to really simplify  your code and focus on the problem you're solving.
  93. 8:58Now, the con for that is as you're  growing the amount of method invocations
  94. 9:03in your computer memory, you can actually run out  of memory. And we'll look at a lot of examples as
  95. 9:08to why this is, and we'll, we'll start to explore  this, this idea of a Stack Overflow exception. And
  96. 9:14this is where we start to run out of  the pre allocated buffer of memory
  97. 9:18that we have for our program, which can  actually cause your program to crash.
  98. 9:26There are a lot of pros when it comes to recursion  when talking about optimization, reducing time
  99. 9:32complexity. And that's what this idea of memos  zation, and we'll look at some examples of
  100. 9:38memorization and caching, to help speed up  redundant calls. And that's a beautiful property
  101. 9:44of recursion. Now, the con again, just like any  piece of code, is that if recursion is overused,
  102. 9:53you can get into this sort of habit where  you start to develop really complex code,
  103. 9:58if it's not well instructed,  and you want to make sure that
  104. 10:02and what we'll get a lot better at this  as we go through this This tutorial
  105. 10:06is when you look at problems, you want to  kind of ask yourself, Is this a good use case?
  106. 10:11For recursion? Can I really break it down into  subproblems that make sense for recursion?
  107. 10:16If you cannot, then you may get into this con  where you have unnecessarily complex code.
  108. 10:24Now, the final thing, as I had  mentioned before, is recursion
  109. 10:27works really well for recursively defined  data structures, JSON objects, trees, graphs,
  110. 10:35things that allow you to basically focus on  one tiny unit of the data structure at a time.
  111. 10:41And it just so happens we'll look at a bunch of  different examples as to why this holds true. But
  112. 10:48this is just a small set of pros and cons, things  to consider when thinking about using recursion.
  113. 10:58This brings us to a topic that I think is  often overlooked when teaching recursion,
  114. 11:04which I think is one of the fundamental  concepts that you really need to grasp
  115. 11:08to understand what people  call quote unquote, magical
  116. 11:12about recursion. It's just magic with how  it works. And after we look at this example,
  117. 11:18we'll start to realize how logical recursion is  and why it's not necessarily such a magical thing.
  118. 11:26Let's imagine that you go to work one day, and  the first thing that you want to do is check your
  119. 11:32email. And whilst checking your email, you get  interrupted by your boss, and your boss says, Hey,
  120. 11:38I need you to go attend some meeting. And  you have to actually deviate from your task.
  121. 11:44So you don't finish checking your email, you get  interrupted. And before you can check your email,
  122. 11:48now you need to attend this meeting. Now, let's  say your boss, well walk into this meeting,
  123. 11:53your boss interrupts you again. And he  says, Hey, our investors are visiting,
  124. 11:58I would love for you to go to this board meeting  and impress them with all your knowledge. And
  125. 12:03so again, we'll walk into this meeting, you get  interrupted, you have to go impress the investors.
  126. 12:10Right, that's your next task. And so before you  can attend the meeting, before you can check
  127. 12:14your email, you need to impress these investors.  And finally, your boss, he interrupts you again.
  128. 12:21And he says, Hey, you know, I'm sorry. But I need  you to go help Jake with his code, his code is
  129. 12:26failing. And we need to push to production. So now  you have to basically avoid the investor meeting,
  130. 12:33avoid the initial meeting, you can't check your  email until you help j code. Once you help him,
  131. 12:39this thing kind of gets popped off your to do  list, you know, you go to the board meeting,
  132. 12:44you attend this original meeting. And  then finally, you can check your email.
  133. 12:50And you may be asking, like, why is this ever at  all relevant to recursion. And it just so happens
  134. 12:56that this sort of idea is exactly how the call  stack works when we're talking about invoking
  135. 13:02methods within our programs. Let's take a  look at this simple program here. Notice
  136. 13:09we have three method calls. And they're kind of  chained together. The first one returns a string,
  137. 13:14but it has a dependency on B, B returns  a string, but it has a dependency on C.
  138. 13:22And c just returns a string. So nothing fancy at  all. Now, the call stack is going to be this sort
  139. 13:28of abstraction that our operating system leverages  to store method invocations within our program.
  140. 13:36It allows us to understand what memory addresses  we return data to, and it stores local variable
  141. 13:42information, like what are the parameters  that were passed into me. So if we execute a,
  142. 13:48the first thing that goes on to the call stack  is this sort of idea known as a stack frame.
  143. 13:54And it basically says, Hey, I want to call  Hello, and I want to concatenate the result of B.
  144. 14:00But in order for me to concatenate the result of  B, I need to actually now call B. So that pushes B
  145. 14:06onto the call stack. Now I'm in the same scenario,  where I want to now return from B, but I have a
  146. 14:15dependency on C. So now I call C and put that  on the call stack. And notice this is the same
  147. 14:21sort of process that we looked at in the  previous analogy. I cannot return or pop
  148. 14:25these things off the stack until I kind of go in  the order in which these things were called. So
  149. 14:32I returned friends, right. And now friends  replaces that see invocation. And so now
  150. 14:37this has been fully evaluated. And so now since  B is fully evaluated, I can return that value
  151. 14:44to the B method invocation. And now that  B stack frame gets popped off the stack.
  152. 14:51And it's only at this point that I have now  evaluated that entire chain of method calls.
  153. 14:57So now I can return this string  value. And get my expected output.
  154. 15:04Now you may be asking, like why still? Why  is this relevant to recursive thinking.
  155. 15:10And let's look at an example. Let's look at a  call stack when we call these recursive calls,
  156. 15:16right? This is just a program that  calls itself and it'll execute forever,
  157. 15:20right? So the first time I invoke a will now  I need to invoke a, but it calls a again.
  158. 15:28And it just keeps happening, right?  There's no stopping condition. And finally,
  159. 15:32there's going to be this point, this point  in time where I try and invoke a again,
  160. 15:37and I get a, I get an error, and  that error is a Stack Overflow.
  161. 15:41And this exception happens when we exceed the pre  allocated buffer of memory that our program has.
  162. 15:48Right, we basically have run out of  memory, we've exceeded the stack,
  163. 15:52and our invocations have overflowed,  and we can no longer handle it.
  164. 15:56And this is the whole thing with recursion, why  we need a base case, we need to return a value.
  165. 16:02So just like we saw previously, for methods  that aren't recursive, we noticed that frames
  166. 16:08still grow and grow and grow. And the only  way for those frames to shrink in size
  167. 16:13is for them to return some value for them to  stop invoking methods. And the same holds true
  168. 16:20with recursion. The only difference is, we have  some sort of base case something that says, hey,
  169. 16:27this is the one thing that I want to condition  on to avoid us from further recursing.
  170. 16:35I want to start off the first technical component  of this presentation looking at recursion with
  171. 16:40strings. And this is going to give us a really  good idea of manipulating input parameters on
  172. 16:47the call stack recursively. And the first problem  I want to look at is this idea of string reversal.
  173. 16:52And so what we do is we have an input string  like the symbol engineer. And the idea is the
  174. 16:57output would be the input in the reverse order,  right? And so the question is, how do we how do
  175. 17:03we build a really concise recursive function that  gives us something like this. And as we look,
  176. 17:10as we look at the skeleton structure for this  code, we of course, are going to have some sort of
  177. 17:15input, right? So the input is going to be some  string, and the output is going to be the reverse
  178. 17:20version of that. And so we always ask kind of  these two questions. The first is, what is the
  179. 17:25base case? And this is really asking, when can I  no longer continue within my algorithm? And the
  180. 17:32next line of code is going to be all about what's  the smallest amount of work I can contribute?
  181. 17:37So in this case, it's going to be basically  between each invocation, what's the small
  182. 17:43unit that I can actually modify or manipulate  to progress a little bit closer to the goal?
  183. 17:51So let's look at the first question. The first  question is saying, When can I no longer continue?
  184. 17:57And I think I think when you think about this sort  of scenario, there are two schools of thought.
  185. 18:03And when I when I like to construct base cases,  I typically think if I were to just pass in a
  186. 18:09very small input, like what is the smallest  input that I could just pass in to start
  187. 18:15to this function, where I would need to basically  immediately return. And there could be two schools
  188. 18:22of thought with this approach, right? And the  two schools of thought could be, well, one letter
  189. 18:28reversed is itself, right. And that could be a  really, really good base case for string reversal.
  190. 18:35But we want to be even lazier. Like what is the  laziest, like the least amount of work that I
  191. 18:41could even consider thinking about. And that would  be the empty string, the empty string reversed,
  192. 18:47is again, just the empty string. And so if I  passed in the empty string to this function,
  193. 18:52and it would only make sense  to get the empty string back.
  194. 18:57And so if we, if we modify the code, and we  look at this, we just have a simple base case
  195. 19:01where we evaluate if the input string is the empty  string, then let's just return the empty string.
  196. 19:08But now we need to consider how do I even  get to that point? What's the smallest amount
  197. 19:13of work that I can contribute? Right? And  that's the question that we're asking here.
  198. 19:17I need to do something that whittles down the  decision space within each recursive call.
  199. 19:25And so we kind of asked this question, what's the  smallest unit that I can deal with in a string? A
  200. 19:31string is just a bunch of characters, right?  And so maybe I can modify a single character.
  201. 19:37And this is where we get to this question. Let  me pick a single character out of this string.
  202. 19:43And maybe where I position it will allow it to be  concatenated from the call stack in the reverse
  203. 19:49order. And it turns out when we when we write  this code, we get a recursive call where we,
  204. 19:56we take that first character from the input string  and we concatenate After the recursive call,
  205. 20:03and you may be looking at this and you say, Okay,  well, the input parameter has changed. And it's
  206. 20:08changed because we've actually shrunken down the  decision space, we've shrunk in that input string,
  207. 20:13because our entire goal is to get closer to that  base case, right. And so we've taken everything
  208. 20:20directly after the first character to the end.  And the idea is that if we do this enough times,
  209. 20:25we can shrink our search space on each invocation  and get the goal, which is the reverse string.
  210. 20:32So this first piece is all is all focused on  shrinking the problem space. And the second piece
  211. 20:38is reflecting really the work that we're doing to  contribute closer to the goal. And I think it's
  212. 20:44worthwhile to analyze what is happening on the  call stack. So let's say that we pass in Hello.
  213. 20:51And we don't hit the base case, on line two, we  immediately go to line six, which is basically
  214. 20:57the recursive invocation with the shrunken down  substring. And then we concatenate the H. And just
  215. 21:04as we had discussed with call stacks, we can't pop  this off the call stack until that recursive call
  216. 21:09has also completed, which adds an  additional stack frame to the call stack.
  217. 21:15And you notice, again, we don't hit the base  case, and we shrink down that input string,
  218. 21:19but we concatenate the first character. And we  keep doing this. And it's nice, because I don't
  219. 21:24need to keep track of the H, I don't need to  keep track of the E. It's all self managed for
  220. 21:29me in the stack frame on the call stack. And so  I invoke reverse string again. And now I get Oh.
  221. 21:37And now watches this gets fairly  interesting, because as I pass in,
  222. 21:40oh, I get to a point where my input string has  been whittled down to just the empty string.
  223. 21:45And it just so happens that for this use  case, this is the base case. So I return
  224. 21:51the empty string. So that reverse  string gets evaluated the empty string
  225. 21:56from this base case, and I end up just  returning the empty string plus Oh.
  226. 22:02And now this recursive call gets evaluated. And  now I add L. And this just becomes o l, right.
  227. 22:09And so now I can return this. And  this gets popped off the call stack.
  228. 22:14And now you can see that I'm basically returning  these values to the stack frame that preceded me.
  229. 22:20And popping things off the call stack.  This function gets evaluated in ADS II.
  230. 22:26So I return it and pop this off the call  stack. This function gets evaluated to o Ll E.
  231. 22:32So I return it, it gets evaluated. And then  this gets popped off the call stack. And as
  232. 22:39you can see, this is the goal, right? This is the  reverse string. And it's the power of the call
  233. 22:44stack the power of these recursive calls that  allow us to return values back down the stack.
  234. 22:51And this is a very good property that  we can exploit when using recursion.
  235. 22:59Let's look at palindromes, palindromes are  these unique words, where we can basically
  236. 23:08spell the same word forward and backward.  Let's look at this word. The mechanical
  237. 23:14way that we kind of analyze if a word is a  palindrome or not, is we look at both ends.
  238. 23:19And we basically say okay, do these letters match?  Yes, they do. So we shrink in Word, we say do
  239. 23:24these letters match? Yes, they do. So we shrink in  Word. And now this is just one character. And it
  240. 23:33proves that we've we've matched successfully.  So this indeed would be a palindrome.
  241. 23:40Now let's look at a snippet of code to see how we  could think about something like this recursively.
  242. 23:48Now, obviously, we're going to have a  Boolean function, because it's either Yes,
  243. 23:52it's a palindrome or No, it's not. And  we're going to evaluate some input string.
  244. 23:59And of course, the first thing we always consider  is what is this base case, the thing that stops us
  245. 24:04from recursing? Now, think back to what  I had said in the previous example,
  246. 24:09I always like to consider my base cases, what  is the smallest input that I could just pass
  247. 24:14into this function? And it turns out very  often not very often, for string recursion,
  248. 24:23you can typically whittle down your search space  and evaluate the input length. So if you passed
  249. 24:27in a palindrome that was of size zero, then  that's a palindrome, right? Because there's
  250. 24:33there's nothing that proves it's not a palindrome.  There are no characters to compare against.
  251. 24:38And in the same vein, if we pass in just a single  character, with a single character, both forward
  252. 24:43and backward is the same character. So that's also  a palindrome. So these are really good use cases,
  253. 24:50or base cases to evaluate this conditional,  which is whether or not a string is a palindrome.
  254. 24:57But let's continue we need to  consider the small amount of work
  255. 25:00that we can do. And in the animation that we just  looked at, you notice that we had two pointers,
  256. 25:06one on the left and one on the right, and we  were comparing those strings, those characters,
  257. 25:10we're saying they need to be the same. If they're  not the same at any point in time, then we've
  258. 25:15violated the property of a palindrome. And if we  violate the property of palindrome, then we just
  259. 25:20go to this false. This is kind of this fallback  base case. So at any point in the algorithm, if
  260. 25:27the characters don't match up at their respective  opposite indices, then we can terminate,
  261. 25:32we return false and the false gets propagated  through the call stack. And the initial color
  262. 25:37function would return the false. But if that's not  the case, then we get to this interesting thing,
  263. 25:46where we call this recursive call, and it whittles  down or sub our substring. So let's look at the
  264. 25:53call stack to understand what's happening. We  pass in race car as an input string. And this
  265. 25:58of course, is a palindrome you can see race  car forward and backwards, it's just race car.
  266. 26:04And the first thing we do is we evaluate  this, this conditional this base case,
  267. 26:08is the length zero or one? Well, it's definitely  not. So we continue. And now we compare the
  268. 26:13first character and the last character for  this input string. And if they're equal,
  269. 26:18then we just call ourselves again,  but we shrink that input string.
  270. 26:24And since these since R and R are equal, we  add another stack frame to the call stack.
  271. 26:29And we've shrunken down our input string,  we again, look at the base case, we don't
  272. 26:34satisfy it. So we compare the first and the last  character. And we notice in this in this case,
  273. 26:40they're also equal. So we call ourselves again,  right, and we don't pop the stack frame off the
  274. 26:45call stack, because we still have more work  to do. And so we shrink down our input string,
  275. 26:51we still haven't satisfied the  base case. And our characters
  276. 26:54at the first and last position are still the  same. So we do just a little bit more work.
  277. 26:59And now we're at the last character. And  we've already come to this conclusion that
  278. 27:05if the input length is zero or  one, then we just return true.
  279. 27:09So for this one particular subproblem, this one  particular element, this is indeed a palindrome.
  280. 27:14So what do we do, we say, yes, this was a  palindrome and we pop it off the call stack.
  281. 27:20And since we return true, and all of these other  conditionals have been satisfied, that we can just
  282. 27:27propagate through the true Boolean down the call  stack. So we return true here and pop this off.
  283. 27:35We go to the next stack frame and return  true here and pop this off. And now we
  284. 27:39get to the final stack frame, which evaluates  to true. And this gets popped off the stack.
  285. 27:49Now I want to look at recursion with numbers.  And the thing that you'll start to realize as
  286. 27:55we go through these sorts of problems, is that  the same blueprint holds true for all of them.
  287. 28:01And the first problem I want to look  at is this idea of converting decimal
  288. 28:05base 10 values to binary, which is a base two  number format, ones and zeros. So the question is,
  289. 28:12how can we convert a number like  25 into its binary counterpart.
  290. 28:18And it turns out, there's a very mechanical way  of doing this. So let's take the number 233.
  291. 28:24And the formula here, as we'll come to find  is we can do a division by two and this is
  292. 28:30basically doing the floor operator. So it gets  us an integer instead of a floating point value.
  293. 28:36And we get we get an output, which is 116. And  we have a remainder, and that remainder is one.
  294. 28:43And the mechanical process that we can  follow to convert decimal to binary is
  295. 28:47we take the results of this operation,  which is 116. And we divide that by two.
  296. 28:53And we just keep evaluating, and this is 58,  remainder zero, and then we take 58. And we
  297. 29:00divide that by two, and this is 29, remainder  zero. And we basically just keep taking the
  298. 29:06result in the output and dividing it by two. And  notice we're keeping track of the remainders here.
  299. 29:13And the interesting property by doing these  operations gets us to a point where we can
  300. 29:18take all of these remainders. So notice, we've  gotten to this base case, which is zero. And that
  301. 29:24kind of halts this progression. So now we're  done. And if we take all of these remainders,
  302. 29:30in the order that they were basically pushed onto  the stack, we can evaluate them and this is the
  303. 29:36result in binary string for 233. And the question  is, how can we Okay, this this mechanical process,
  304. 29:44you know, very formulaic? How can we  convert it into some recursive operation?
  305. 29:50Let's take a look at some of this code. Now,  notice, we said the first thing is we took 233
  306. 29:57divided by two, we got some output The remainder  was one. And so there are actually many ways
  307. 30:06of doing this problem. And we're going to keep  it really simple. So notice we're dealing with
  308. 30:09strings. As the result outputs, we're going  to be concatenating strings basically.
  309. 30:16And so what we consider here is we we think  about that base case, right? If I hit zero,
  310. 30:23then there's no longer reason for me to  divide further, right, I return my result.
  311. 30:31Now, if I have not hit zero yet, it means that I  still have more division to do. And so the first
  312. 30:38thing that we do is we get that remainder. So  when we look at 233, divided by two, we want to
  313. 30:45store that remainder because that represents  one of the binary digits that we care about,
  314. 30:50basically tells us if the value is  even or odd. And this is the binary,
  315. 30:54this is the remainder that will contribute to  the result. And so that's why we store this
  316. 30:59in the result. And notice, we just prepend this  to the result. And now what I do is I again, just
  317. 31:06call find binary and I shrink my problem space  by half. And I just propagate the result through.
  318. 31:14Now let's let's actually look at the call  stack. By coding this. And seeing what it
  319. 31:19looks like I want to take the code that we  were just looking at, but look at it from
  320. 31:23the perspective of the call stack and understand  how these stack frames are building up over time.
  321. 31:30Now, there are many ways to code this fine binary  function. And a lot of people do it returning an
  322. 31:36integer. And that's completely fine. And if you  want to do it that way, you can actually follow
  323. 31:40along with any modification of this and analyze  the call stack with me. So as we run in debug,
  324. 31:48the call stack is going to show up down here. So  these are all the stack frames that we can watch
  325. 31:53grow. And the variables on the current stack  frame will show up under here in local. And so
  326. 31:59as we dive into this function, you notice that  this is the first stack frame for find binary.
  327. 32:05And in order for this to complete,  we need to return some value.
  328. 32:10We haven't hit our base case yet. So we jump into  our work. And we just make another recursive call.
  329. 32:16So the first stack frame can't pop off yet,  because we're going to invoke ourselves again.
  330. 32:20And once we do that, you notice  that we add another stack frame.
  331. 32:24And also notice that the input parameters have  changed. We've shrunken down our decision space,
  332. 32:28and we've appended the first digit to our binary  result. We haven't hit our base case yet. And we
  333. 32:34continue. And again, we do a recursive operation.  And our decision space keeps shrinking. And this
  334. 32:42is the same mechanical process that we saw in the  animation, we divide by two and log the remainder,
  335. 32:48we divide by two and log the remainder. And we  do this until we hit some sort of base condition.
  336. 32:54Right. And as we shrink down our problem  space, notice we get to this point where
  337. 32:59the decimal is one. And that still is  not our base case. So we go through,
  338. 33:05we get the result. And this binary string  is looking pretty good. In on the final
  339. 33:11recursive call, you notice the decimal is  now zero. And this is a really good case now,
  340. 33:17because now we can just return. So we  come in here and we return this value.
  341. 33:23And this value comes in and says okay, find binary  for that invocation returned this result. And so
  342. 33:30now we're just saying, okay, continue. And notice  as I step through, the stack frames have grown.
  343. 33:38But now they should shrink off the call stack. So  I step through. And notice they've all shrunken
  344. 33:44down, so all of them have returned, they've all  returned a value. And now once I step over here,
  345. 33:54we noticed that the binary  value has been evaluated,
  346. 33:57you can come into this debug  console and look at it.
  347. 34:02And this is the resulting binary string. And so  it's good to look at how the call stack is working
  348. 34:08to understand. Okay, how many stack frames do we  build up to get to our result? And how do they
  349. 34:13unravel once we hit our base case here, we've just  straight up propagated the result down through
  350. 34:19all of the stack frames, and all of them just  return the same thing. And we'll look at a lot of
  351. 34:23different examples where all the stack frames kind  of work together. And they wait for the result to
  352. 34:28do a little bit more work. And so there's a lot  of different versions of this. The next problem
  353. 34:34that I want to look at with numbers is the sum of  natural numbers. And the idea of this problem is
  354. 34:41you take an input number like 10, and then you sum  all the values up to 10. And what we do here is we
  355. 34:50actually just add them all up, and we get some  output and the output in this case would be 55.
  356. 34:56And the question is how can we build some  succinct function that Does this recursively
  357. 35:03Now let's look at a little snippet of code and try  to understand what's happening behind the scenes.
  358. 35:10So recursive summation, again, we take in an input  value. And the first question we need to ask is,
  359. 35:16what is the smallest input value that I could  pass in. So if I pass in one, for example,
  360. 35:23the sum of one to one is just itself. One,  right. And so that's a good, that's a good place
  361. 35:32for the base case. And again, keep in mind, we  are simplifying these functions. So you know,
  362. 35:38edge cases, we're not really focused on that.  Right, now we're focused on the core goal,
  363. 35:42which is building a succinct function. So  if I pass in one, I would return one. Now,
  364. 35:48if I pass in a larger number, like 55, I still  have a lot of work to do, I need to add up
  365. 35:53all the proceeding numbers up to 55 from one. And  so what I can do is I can take whatever value I am
  366. 36:00currently. And I can add it to myself again,  but subtracting one from that numbers, right.
  367. 36:07So it shrinks me closer to this base case. Let's  again, look at some code and understand how the
  368. 36:13call stack is working with this sort of code. So  I've taken the same code from the slide. And now
  369. 36:19we want to look at the call stack and understand  what's happening as we make these recursive calls.
  370. 36:24The first thing that we'll look at is the number  five, so we want to add all values from one to
  371. 36:29five. Let's put a breakpoint here and debug into  the call stack. So as I dive into this operation,
  372. 36:36we first evaluate the base case, which hasn't  been hit yet. And so now what I want to do is
  373. 36:41I want to take five, and I want to add it to  another recursive call, but that recursion
  374. 36:46is shrinking down the input space again. And  so as I dive in, notice this input number
  375. 36:51change. So I make the recursive call, and  the input number has shrunken down to four,
  376. 36:56and another stack frame has been added to the  call stack. And so we keep evaluating has the
  377. 37:01conditional been hit? No, it has not. So I  come in here and it shrinks down to three.
  378. 37:06And I keep doing this, and I come in here. I  come in here. And now the input number is one
  379. 37:13has the base case been hit yet? Yes, it has. And  so if I dive into this, you notice that we return
  380. 37:20one here. And this one returns this  value to the stack frame right below it.
  381. 37:26And so notice that as I continue, that stack  frame gets popped off. And you can see that the
  382. 37:32invocation of this method returned one here. And  so now what I'm doing is I'm taking two plus one,
  383. 37:39and I'm returning this value and  this value is going to continuously
  384. 37:43unravel through these stack frames. So  notice, all of these get popped off.
  385. 37:50And here's the final invocation, the input  number is 10 here. And as we continue,
  386. 38:01this is evaluating for recursive summation of 10.
  387. 38:08And so now again, for 10, we get down to a base  case. And that base case is one. And if we look,
  388. 38:16so I'll put a breakpoint here. And we continue, we  can see that we have the results of 15 and 55. And
  389. 38:26these get printed out. And so again, this is just  an example to show as we're looking at the stack
  390. 38:31frames, how a stack frame can return a value to  its previous stack frame to finish the operation.
  391. 38:38And that's the key here for recursion. And we'll  keep doing this for a lot of the problems that
  392. 38:43we look at to get familiarized with how the call  stack is working, and how the stack frames grow.
  393. 38:51I want to talk about divide and conquer  algorithms, which are really great demonstrations
  394. 38:56of recursion. And the blueprint that we think  about for dividing conquer holds through so
  395. 39:03we you know, still divide a large  problem into several small problems.
  396. 39:08But divide and conquer is all about we divide  them into subproblems. We independently solve
  397. 39:13them. And then we actually merge the  results to solve some holistic problem,
  398. 39:18right? We merge them together and say, Hey,  this is the solution. And they are typically
  399. 39:23recursive. And let's look at the first one,  which is binary search. And the entire purpose
  400. 39:30of binary search is we look at a sorted list of  numbers. And the key point here is they're sorted.
  401. 39:37And let's call this array. And the first  thing that we say is we say alright,
  402. 39:41I'm going to start with the left and right  index, so the far left index is zero and the
  403. 39:45far right index is the length of the array minus  one. And the whole purpose of binary search is to
  404. 39:52find a value we want to find a value in this  array. So we know that zero is less than the
  405. 39:58length of the array. So we can Have not hit  our base case and we calculate the midpoint.
  406. 40:03So the midpoint is the left and right index added  together and divided by two. And so we check, we
  407. 40:10say, okay, is that midpoint? The answer that we're  looking for? So that's another sort of base case,
  408. 40:16have we hit or found the number 10? Here? And the  answer is no. So we ask one of two questions, the
  409. 40:24first question that we ask is, is the number that  we're looking for on the left half of the array,
  410. 40:30or is the number that we're looking  for on the right half of the array.
  411. 40:34And remember, we just found the midpoint, so  we're considering everything to the left and
  412. 40:38the midpoint and everything to the right of the  midpoint because the input data is sorted. Now,
  413. 40:44if it's on the left half, which is what we're  looking at here, then we completely discard
  414. 40:49the right half. And notice, the way that we do  that is we change the bounds of the problem.
  415. 40:56So the left half stays the same. So our  starting point on the left stays the same,
  416. 41:00but we only consider up to the midpoint minus one.  And so that is our ability to shrink our decision
  417. 41:08space and completely discard the right half in  each recursion, or each recursive invocation.
  418. 41:15Now, if we get to this other evaluation, this  is basically saying the value we're looking for
  419. 41:19is on the right half. And what this allows us to  do is we only consider our starting point to be
  420. 41:25the midpoint plus one all the way to the  end. So we completely discard the left half
  421. 41:30for the rest of this problem. And  this indeed, is also a recursive call.
  422. 41:34So look at the first stack frame in the  call stack to the right, we start with zero,
  423. 41:40our upper bound index for the right variable is  nine. And the number we're searching for is 10.
  424. 41:49So here's our midpoint, this is  three. And we asked this question,
  425. 41:53Is this the value that we're looking for?  Is this 10? And the answer is no, it's not.
  426. 42:00So what we can do is we can discard the  entire left half of the search space because
  427. 42:0510 is greater than the midpoint. And it turns out,  we add another stack frame to the call stack and
  428. 42:12notice how the parameters have changed. Now for  the left, the starting bound, which is the left
  429. 42:17is five, which is the index where four is,  and the upper bound is nine, which is the
  430. 42:23original length of the array that we're working  with. And again, we calculate the midpoint,
  431. 42:28the midpoint on this sub array is nine, and we  say is 10, greater than nine or less than nine.
  432. 42:35And of course, we know 10 is greater than nine.  And so what we can do is completely discard
  433. 42:40the left half of that sub array and continue. And  we add another stack frame. And the stack frame,
  434. 42:48the start bound is eight in the upper bound is  nine. And we again, recalculate the midpoint.
  435. 42:55And the midpoint at this point actually turns  out on line seven, we've hit this base case,
  436. 43:00the base case here is that we've found  the number that we're looking for. And
  437. 43:03this is the solution. And so what  we can do is just return this value.
  438. 43:08But we're not done yet, because we need to still  consider the call stack. And so this value is 10.
  439. 43:14And so what we do from this stack frame is we  return 10. And in this case, we returning the
  440. 43:21index that 10 is app. So 10 is at the eighth index  of the array, and this gets popped off the stack.
  441. 43:28And now this binary search invocation has been  completed, and it gets eight. So it just returns
  442. 43:34eight. And this gets popped off the stack. And  now finally, we return eight. And we're complete.
  443. 43:42And the final stack frame gets popped off the  stack. And that's how binary search works. Let's
  444. 43:48look at Fibonacci, a classical, mathematical and  computer science problem that often demonstrates
  445. 43:55the power of recursion. And we're going to  be looking at the non optimized version here.
  446. 43:59And we'll add some optimizations at the end. And  let's look at the mathematical expression. First,
  447. 44:05we're basically saying that for some input in at  the index n is going to be made up of the sum of
  448. 44:13the two values at the two indices that precede it.  Let's look at the Wikipedia page for Fibonacci.
  449. 44:20And this is the sequence the Fibonacci sequence.  And if we take one of these numbers like 55,
  450. 44:27it's the sum of the two numbers that  preceded so 34 and 21. And if we look at 34,
  451. 44:34it's the sum of 21 and 13. And if we look at 13,  it's the sum of eight and five, etc. And this,
  452. 44:40this formula holds true for all values of one to  infinity. And so this is the Fibonacci sequence.
  453. 44:47So if we look at this expression again, now we  have these base cases in pink, and basically says
  454. 44:53that for the values of at index zero and one,  the base cases are zero and one was Effectively,
  455. 45:00and we can just return at that point. So that  would be where we stopped that recursion.
  456. 45:05And the piece of yellow is just saying that this,  this holds true for all values of one to infinity.
  457. 45:12So let's let's kind of look at this and  understand why it differs from the previous.
  458. 45:18Now we're dividing and conquering, right, we're  dividing this problem, we have fib of n minus
  459. 45:24one that needs to happen, we add it to fib of  n minus two. And these are two recursive calls.
  460. 45:30And as we think back how the call stack works,  we know that the recursion on the left needs
  461. 45:35to complete before we even considering, consider  starting the recursive call on the right. So fib
  462. 45:43of n minus one could have a ton of different  calls that need to complete before we even do
  463. 45:48the plus operator to fib of n minus two.  So let's look at how this would animate.
  464. 45:54So we want to find the Fibonacci of five. And like  I had said, we do not evaluate the right hand side
  465. 46:01of the expression yet, because we need to satisfy  that first recursive call first. So this gets us
  466. 46:08F, fib of five minus one, which is four. And  this gets us four minus one, which is three.
  467. 46:16And this process continues F of two. And remember  our base case, f of one is just one, so we would
  468. 46:23return from here. And now to can evaluate the  right hand side of its recursive operation.
  469. 46:29Remember, we're calling the same function over  and over again. So now we've hit a base case,
  470. 46:34we've evaluated the left hand side, which is  fib of n minus two, or fib of n minus one. And
  471. 46:41now we can do fib of n minus two for this value,  which is f of zero. This again is a base case.
  472. 46:46So we can return from here. Now, the sum of these  two values return and get passed up to F of three.
  473. 46:54And now F of three can evaluate its recursive  call on the right hand side of that plus operator.
  474. 47:00And again, we do f of one, that's a base  case, so we just return it. Right? Now this,
  475. 47:07these F of two, f of one get added together to  return from F of three, and this gets passed to f
  476. 47:11of four. And now F of four can evaluate the right  hand side of its expression, which is f of n minus
  477. 47:18two. So here we get F of two. And again, this  recursive property holds true, this is a base
  478. 47:25case, we return it, we evaluate the right hand  side, this is a base case, we return it. And
  479. 47:30now this gets propagated back up to F of four. And  these values get propagated back up to F of five.
  480. 47:37And it's from here. Now, this is the very first,  you know call of the function, we can now evaluate
  481. 47:43the right hand side of that plus operator. And  again, we get down to f of three, we get down
  482. 47:48to f of two, and then this is f of one. And  this is a base case. So we return it and this
  483. 47:54is f of zero, this is a base case, we return  it. And notice like we're doing the same thing
  484. 47:58over and over again, this gets returned. Now we  go to the right hand side, this gets returned,
  485. 48:04this gets passed up. And now we can add these  two values. And that's how we find the Fibonacci.
  486. 48:11Now, we'll look at optimizations for this later.  But I just want to point out one thing like as we
  487. 48:17evaluate this, notice, we have f of three here,  we have f of three here. This is redundant,
  488. 48:23right? All of the nodes below F of three are the  exact same. And so it seems extremely wasteful,
  489. 48:30that we're recalculating these values. And so as  we'll come to realize, there are optimizations
  490. 48:35that we can consider to avoid recalculating  something that we've already done the work for.
  491. 48:40So if you look, we have f of two, f two and  f of two. And this is three nodes. And you
  492. 48:46can imagine for really large numbers,  it turns out for Fibonacci, you know,
  493. 48:50most modern day computers cannot run Fibonacci  for this function at a very high input. And that's
  494. 48:58because the recursive calls are so intensive. So  we have to look at some optimization techniques
  495. 49:04known as memoization. Merge Sort is this poster  child of divide and conquer when explaining this
  496. 49:14in a lot of computer science classes. And the  idea is that we take in a bunch of unsorted
  497. 49:20values, like the following. And the idea is  that we divide this array in such a way that we
  498. 49:27keep dividing. And then we merge up the sorted  results to sort this array in ascending order.
  499. 49:34And it kind of looks like this. So we split the  array in half and we say, Alright, I'm going
  500. 49:37to focus on the left hand side. And remember,  before I even do the right hand side, I need to
  501. 49:43finish the left hand side first, right? That's the  order in which recursion is going to operate here.
  502. 49:49And this process just holds through. So this is  the left hand array and what do I do I split this
  503. 49:53in half. And now I decompose this into two parts.  But again, I first have to focus on this half. And
  504. 50:02then I can focus on this half. So I look at, you  know, I split these, and I look at foreign one.
  505. 50:09And the base case here is that you  can't really soar just one value.
  506. 50:14And so I can stop splitting when I hit  just one value. And I compare these two,
  507. 50:19and I merge and sort them together  just by a simple comparison operator.
  508. 50:24Now I can look at the right hand side, and I  basically split these. So I have one integer here,
  509. 50:30and I take two and zero, and two and zero gets  split even further. And again, I'm down to this
  510. 50:37case where I just have digits, and so zero to  get compared, they get merged up. Now three has
  511. 50:45a linear time comparison, again, zero and two,  and these get merged together. And now we take
  512. 50:51one and 402, and three, these get merged together.  And now I've solved the left half of this array.
  513. 50:59But remember, now I need to do the right half,  we're evaluating in the order that recursion
  514. 51:03would consider this. So when we split this, I get  the right half of the array. And I do the same
  515. 51:08thing on this side. So I take this array, split  it, I get negative one and seven. This gets split,
  516. 51:16I compare them. And it just so happens, they were  already in sorted order. So these get put back in
  517. 51:21the same spot. Now I take the right half of the  array, right, and I split this even further.
  518. 51:30And so when this gets split, I  have 10, nine and 20 gets split,
  519. 51:34nine and 20 are individual digits. And when I  compare them, they are already in sorted order.
  520. 51:40And then I compare all the digits here,  and they get merged back in sorted order.
  521. 51:45And then again, finally I compare these digits,  and they get merged back in sorted order.
  522. 51:52And this brings us to the final merge,  remember divide and conquer is all about
  523. 51:57dividing your problem into subproblems.  And then merging the results together.
  524. 52:02So what we've done is we've just recursively  merged all the results together from the
  525. 52:07recursive sub calls. And this is the final merge.  And just to emphasize how this comparison works
  526. 52:14to ensure that these things get put back  in sorted order, we do a linear comparison.
  527. 52:21And what that means is we basically have two  pointers, starting with the left hand side
  528. 52:25and we say alright, which number is smaller, and  we know negative one is smaller, so we put this
  529. 52:31in its spot and increment the pointer. And then we  compare these two values will zero smaller here.
  530. 52:36So we put that in spot and increment the pointer.  And we keep doing this as we compare values.
  531. 52:44And notice here, we've actually run out of  values in the left hand array. And since
  532. 52:49we already know the right hand array is sorted,  we just placed these back in the positions they
  533. 52:54belong. And as we look at the resultant array,  we noticed that Merge Sort has sorted the input
  534. 53:00completely. And so this right here is the sort of  solution. And now the question is, how do we build
  535. 53:09something like this? How do we devise a recursive  algorithm that could construct a sorting,
  536. 53:15you know, solution to a problem like this,  we're gonna look at some code to do that.
  537. 53:21So we have a blank slate here. And we want to  consider how Merge Sort would work to satisfy the
  538. 53:27properties that we looked at. Now, the first thing  I want to do is build the recursive call. And so
  539. 53:34remember, Merge Sort takes in an array, and it  sorts it. And we're going to do this in place. So
  540. 53:40I'm going to build a function that's public, and  static. And it's just going to be void. So we're
  541. 53:46going to modify the original input array. And what  I'm going to take in this is going to be called
  542. 53:52merge sort. And it's going to take in just a one  dimensional integer array, and we'll call it data.
  543. 53:58And it will have a start, and an end, which will  represent the indices that we're working with.
  544. 54:06Now, for merge sort, this is going to be a  recursive call, we need to consider the base
  545. 54:11cases. Remember, we work from the start  to the end. And if those values overlap,
  546. 54:17then we've hit our base case. So we asked  this question, if start is less than end,
  547. 54:21and we can continue doing work. But if the  start passes, whatever the end pointer is,
  548. 54:27then there's no more work to continue.  We've already sorted the data.
  549. 54:32And so remember, we're all about taking the  array and splitting it in two halves, we want to
  550. 54:38divide the problem into two halves and solve them  independently. And so the first thing that we can
  551. 54:42do is we can calculate the midpoint. And so what  we do is we say in mid is going to be basically
  552. 54:48whatever the start index is of the array plus  the end index, and we divide that by two,
  553. 54:54and that's going to be the  midpoint that we're working with.
  554. 54:58And what do we want to do? Here, well, what  we want to do is we want to divide the array
  555. 55:03in two parts. And so it would make sense for us to  just call merge sort. We're dealing with the same
  556. 55:09data array. But our bounds are what has changed.  And so the start is again, for the first half
  557. 55:17going to start at the same position, but  the end is going to end at the midpoint.
  558. 55:24And that's going to be the left sub half.  Now, if we think about the right sub half,
  559. 55:29the start is going to change, right? The start  is going to be whatever the midpoint is plus one,
  560. 55:34all the way to the end. And this is how we can  consider splitting this array into two different
  561. 55:40sub halves. But the question is, how do we how  do we merge this data, right, what we're doing
  562. 55:47is we're just continuously dividing and dividing  and dividing, but I want to be able to merge the
  563. 55:53data in sorted format. So let's build a function  called merge. And what merge is going to do,
  564. 55:59it's going to take the original data array, and  it's going to start taking the start the midpoint
  565. 56:05in the ending index. And remember that  last animation that we just looked at
  566. 56:10does this kind of linear time comparison  to replace the values in the correct spot
  567. 56:17when it's looking at the two sorted sub arrays, so  I build a function here, it's going to be public,
  568. 56:25static void. And I'm just going to call this  merge. And again, it's going to take in an
  569. 56:30integer array called data, a starting  point, a midpoint and an end point.
  570. 56:39And now remember, we basically need to merge these  values, but I don't want to modify the input array
  571. 56:45yet. So I'm going to build a temporary array. So  it would make sense to build a temporary array
  572. 56:52to avoid modifying, can't type to avoid modifying  the original contents. And so in order for me to
  573. 57:02do that, I can just build a simple temporary  array here. And it will be a new int array.
  574. 57:08And the size is just going to be dependent on the  indices, right, so I have n minus start, plus one.
  575. 57:17So this is going to build me a pre  allocated buffer of memory to hold
  576. 57:23all the data for the sub arrays that I'm  dealing with, from the start and end index.
  577. 57:31And now what I'm going to do is I'm just  going to copy the values. So I want to I
  578. 57:36don't want to lose a reference to the start or  midpoint. So I'm going to say int i equals start,
  579. 57:41we'll say j is equal to mid plus one, and k is  equal to zero. and k is going to be this kind
  580. 57:48of tracker variable that we use to keep track  of the values that we put into this temporary.
  581. 57:57So let's continue. Now recall back when we were  merging the data together in that final sub call,
  582. 58:04that's a good way to kind of realize how this is  working. So we're basically doing a linear time
  583. 58:10comparison, at the values in the left array, and  the pointer on the right array, and we're saying
  584. 58:16which one is smaller, whichever one is smaller  is going to be placed first in this temporary.
  585. 58:23And so I'm basically saying, while i is less  than or equal to mid, which is going to be
  586. 58:29the left sub array. And we want to say,  well, j is less than or equal to the end,
  587. 58:38then we can continue. And what is this  saying? What this is saying is while both
  588. 58:44of the sub arrays have values, then  try and merge them in sorted order.
  589. 58:51Right, and that's what we want to do. So  we're starting with I, which is the left
  590. 58:55sub array up to the midpoint, and then j,  which starts from the midpoint to the end,
  591. 59:01is going to be the right sub array. And  we just want to compare these values.
  592. 59:05And so we ask a question, we say, Alright, well,  if data sub i is less than or equal to data sub j,
  593. 59:15then we know what we know that the value in the  left sub array is less than whatever the value
  594. 59:20is in the right sub array. So what we can do is  we can say, Alright, in this temporary buffer,
  595. 59:25I'm going to put in at the index k data  sub i. So the smaller value gets put
  596. 59:33next in the temporary. And now what I want  to do is I want to increment i, since I've
  597. 59:38already placed it in the array, and I want to  increment K, so I don't override this value.
  598. 59:45Now if this condition doesn't hold true,  then what I can say is well, okay, so
  599. 59:49that would imply the opposite. So I would just  say temp of K is going to equal data sub J.
  600. 1:00:07And then what we would do is we would say k plus  plus, and j plus plus. And you can also get fancy,
  601. 1:00:14you can come in here and do something like k  plus plus here. And you can do something like
  602. 1:00:18j plus plus here, it's both, it's going to be the  same thing. So if you want to simplify your code,
  603. 1:00:23that way, you can do the post increment  operator to reduce the amount of code.
  604. 1:00:30And this handles basically the comparison between  the sub arrays. But remember the conditional here,
  605. 1:00:36the conditional says, we only do this while both  values have values to compare against. And the
  606. 1:00:44example that we looked at, we actually ran out  of data in the left sub array. And we just had to
  607. 1:00:50just blindly place all the data in the right sub  array into the original array. And so we need to
  608. 1:00:56satisfy that case here. And in order to do that,  we can just say, while i is less than or equal to
  609. 1:01:02the midpoint, so while there's basically data  to traverse, still, we're just going to place
  610. 1:01:08it into the position of the temporary array  where it belongs. This would be data, so I,
  611. 1:01:16and again, we would do k plus plus n i  plus plus. And that handles exhausting
  612. 1:01:24the left sub array, if the right sub  array has run out of values, so we say,
  613. 1:01:29add the rest of the values from the left sub  array into the result. Now on the opposite side,
  614. 1:01:39if the left sub array has run out of values,  but we still have data in the right sub array,
  615. 1:01:45then we just need to have the reverse conditional  and we say, while j is less than or equal to end,
  616. 1:01:50then we're just gonna say temp  sub k is equal to data sub j.
  617. 1:01:57And then again, we just increment K. And  we increment J. And this is the same thing,
  618. 1:02:08we're adding the rest of the values from  the right sub array into the result.
  619. 1:02:16You may ask, Are we done yet? And we're almost  done. But we need to remember we built this
  620. 1:02:20temporary array, and this is void. And so we  haven't really done any work yet. And so the
  621. 1:02:26question we need to ask is, how do we modify the  original data array in memory. Now, we don't want
  622. 1:02:32to modify the entire array, we only want to modify  the subsection that we're dealing with right now,
  623. 1:02:37in whatever recursive call that we're in. And  so this brings us to the copy phase, how do
  624. 1:02:43we copy this data over from temp into the right  positions of the data, which is the original data.
  625. 1:02:50And it turns out, we can just have a simple  for loop where we say i is equal to start.
  626. 1:02:56And while i is less than end, right, so we're only  taking the subsection that we're dealing with.
  627. 1:03:01So from start to end, right, which could be  any, any bounds at any point of the recursion.
  628. 1:03:08So while I is equal to start n is less than or  equal to end, we're just going to increment i.
  629. 1:03:14And in each iteration, we're going to load  up the original data array at the index i
  630. 1:03:20to be equal to whatever's in the temporary  array at the value of i minus start.
  631. 1:03:28So if the data array that we're dealing with, if  the start is 12, then what we would do is we would
  632. 1:03:34say, all right, well, I equals 12. So 12 minus  star is zero. So we're going to load data set 12,
  633. 1:03:41with whatever's at temp sub zero. And this  is the copy phase. This is how we actually
  634. 1:03:46override values at each sub array.  Let's actually run some input here,
  635. 1:03:55I'm going to have a new input array, we'll say,  you know, data equals new int. And we'll just load
  636. 1:04:02it with some values, we'll say negative five,  you know, 2010 320. And what I want to do is
  637. 1:04:11I want to sort this. So I'm going to call merge  sort. And I'm going to give it the data array,
  638. 1:04:19the start is going to be the zeroeth index. And  the end is going to be the length minus one.
  639. 1:04:27And so this should sort the array in place.  And so what I can do is I can put a breakpoint
  640. 1:04:34to look at the array once it's been  sorted. And then we'll look at the
  641. 1:04:37call stack to see what actually  is happening behind the scenes.
  642. 1:04:44So I'm going to debug and we'll notice I  get here and merge sort has completed. And
  643. 1:04:51what I can do is I can look at this stack frame  and look at the data. And you notice that we
  644. 1:04:55have in the zeroeth index, negative 5023 10 and  20. And so this is the output in sorted order.
  645. 1:05:06But this doesn't really do us any good unless  we understand what's happening at the level of
  646. 1:05:10the call stack. So let's take a look at that.  By put a breakpoint in the merge sort function.
  647. 1:05:18Let's kind of look and see what's happening. If  I dive in here, the first stack frame gets added
  648. 1:05:26to the call stack for merge sort. And I basically  ask is start less than end? And the answer is no,
  649. 1:05:32we still have work to do. And so when I go in  here, I calculate the midpoint. And I say I want
  650. 1:05:37to divide this array into two sub halves. And the  midpoint here is two. And so what I do is I say,
  651. 1:05:44Alright, I'm going to divide everything from  zero to two into its own set of sub arrays,
  652. 1:05:49and then I'll handle the right half. And remember,  I can't even go to the right half until the left
  653. 1:05:54half has completed. So I dive into this. And  now I'm looking at everything from zero to two.
  654. 1:06:02And I have to break this up even further. And  so now I'm at this position where the midpoint
  655. 1:06:06is one. And again, I grow the call stack,  and I'm still only on the left hand side.
  656. 1:06:12And I calculate the midpoint again, and I  go again. And now we don't satisfy this,
  657. 1:06:17right, because zero and zero are equal. And  so we actually pop this off the call stack.
  658. 1:06:25And now we return to the next recursive  call. And the next recursive call says,
  659. 1:06:29Okay, now I want to handle  the right hand side of this,
  660. 1:06:32right. And when I dive into this, I grow the call  stack again. And again, we pop off, right, we
  661. 1:06:38don't satisfy this base case, and we kind of just  continue this operation. And so now I've reached
  662. 1:06:44the basically the base case on the far left hand  side of the first left sub half of this array.
  663. 1:06:51And what it's asking me to do is it's asking  me to now merge these values in sorted order.
  664. 1:06:56So if I dive in here, and we look at the merge,  let's analyze the start the mid and the end.
  665. 1:07:05Remember, I'm only dealing  with basically two values here.
  666. 1:07:10And so if I go in here, we have a temporary  array, and it's only of size two. And that's
  667. 1:07:15because my base case here for merge sort is only  really comparing two values. And so we go in here,
  668. 1:07:21and we say, all right, which one's smaller, the  left value, or the right value. And I come in
  669. 1:07:27here, and I say, Okay, well, here, the left value  is smaller. So I load up K, I load up temp, and I
  670. 1:07:34put in negative five. And then I have another  while loop. And remember, because I ran out of
  671. 1:07:40values to compare in the left sub array, I need  to basically take all the values in the right sub
  672. 1:07:45array and put them in the positions they belong.  So I come down here and I load up the values,
  673. 1:07:51increment the counters. And now we're  done, because we've hit this base case.
  674. 1:07:59So now, I have this sorted sub array, negative  five and 20. And what do I need to do I need to
  675. 1:08:05put these in the original spot, right? If we look  at these values, I have, you know, I, J and K.
  676. 1:08:11What does that mean? Well, it means that  from the position that I'm dealing with,
  677. 1:08:16in the original array, I need to override those  values. So that's what this replacement is
  678. 1:08:20going to do. So coming here, I replaced value  one, I replace value two. And now I'm done.
  679. 1:08:31And that is the process that we go through  for a single iteration of merge sort. And
  680. 1:08:38this process will basically continue as  we are dealing with bigger and bigger sub
  681. 1:08:43arrays as we recurse back from the bottom  up, right, and now we're merging again.
  682. 1:08:49And notice, like, since we did the left sub half,  which was just two values, now we're dealing with
  683. 1:08:55the right sub half, right. And now we have three  values, and the right sub half of another sub
  684. 1:09:03problem. And so we just continue comparing and  going up and going up. Now, we won't go through
  685. 1:09:08every stack frame. In this call. There are quite  a few as we saw in the animation. But I encourage
  686. 1:09:15you to rewatch that animation because that is  the order that the call stack will be evaluated.
  687. 1:09:21And that's the order in which things will recurs  back up from the subproblems to get merged into
  688. 1:09:27some overall solution. And that's one of  the key components of divide and conquer.
  689. 1:09:35linkless are really common data structures used  to store data that is not necessarily contiguously
  690. 1:09:42stored in memory. And it turns out, you can do a  lot of cool recursion on these sorts of things.
  691. 1:09:49We're going to look at link list reversal.  And we'll look at an animation to walk through
  692. 1:09:54this code. Now the idea is that we have some  sort of linked list. Let's say we have values
  693. 1:10:01in sorted order like this. And we'll just have a  bunch of pointers. And the idea is that the head
  694. 1:10:08node changes from one pointing to two pointing to  345. Instead, we want six to point toward five,
  695. 1:10:14five, to point toward four, and so on. And when  we look at this code, this reversal code, we asked
  696. 1:10:23this question, all right, if the head node is  null, right, if we pass in a null value, or the
  697. 1:10:30next value from the head is null, then we just  return the head. And this kind of brings us back
  698. 1:10:36to that idea of considering base cases. What's the  smallest thing I could pass in? If I passed in?
  699. 1:10:42No, then I can just return null. And that would  be a reversed list. Right? And in the same vein,
  700. 1:10:49if I passed in just a single node, where there was  no next node, then I could just return head again.
  701. 1:10:55And those are good base cases for this solution.  But the question is, how do I even start the
  702. 1:11:01reversal? What's the unit of work I need to  do and that's what we're going to look at.
  703. 1:11:06So we start with one, the head node that we  pass in on the first iteration is one. And we
  704. 1:11:12don't hit the base case. And so on line three, we  see that we call reverse list head dot next. And
  705. 1:11:18that immediately pushes us to the next node.  And we have one stack frame on the call stack.
  706. 1:11:25Now we're looking at two and this, this is not  know either, and it also doesn't have a pointer
  707. 1:11:31pointing to null. And so we execute line three,  again at another stack frame on the call stack.
  708. 1:11:37And that gets us to three. And this process  continues all the way until we get to six.
  709. 1:11:44And when six gets passed in as a parameter,  we say is it null? And the answer's no,
  710. 1:11:48but the next value is null. And so what  do we return, what we do is we return
  711. 1:11:53head, which is just six, and six gets returned  as the node to the previous value. So this is
  712. 1:12:01the base case. And we return this to five. And  so now P has been evaluated to the number six.
  713. 1:12:10And what we're saying since since the number  five is the current head node, we're saying
  714. 1:12:15five dot next dot next, equals five. And it turns  out that what that means is we're basically just
  715. 1:12:22saying, Okay, well, what I want you to do  is take six and have it point back to five,
  716. 1:12:28because head next to six, and head dot next, which  is six dot next, is going to be pointing to five.
  717. 1:12:37And if that doesn't make sense, I encourage  you to really slow down and read this code.
  718. 1:12:42And think five is the head node. And we want  to say the next pointers, next position just
  719. 1:12:48points back to ourself. Now the problem here  is that five is is also pointing to six.
  720. 1:12:55And if we were to trace this, this is this would  be a cyclic dependency. And this would never end
  721. 1:13:00right. And so what we can do is we can just say  fives dot next, can point to nothing, right? This
  722. 1:13:05is no, and so that gets dropped off. And what we  return is we return six, because we want six to be
  723. 1:13:12the new head. And so six will just get propagated  down the call stack to the original color to be
  724. 1:13:18the new head node. And that's why we return p  here. And so now we return and we get to four.
  725. 1:13:28And when we look at four, we know that P is  six. And what we're asking here is we're saying
  726. 1:13:36fours, next note is five, and I want five  2.4. And so again, we do the same thing,
  727. 1:13:43we take five, eight points to four. And to avoid  the cyclic dependency, we drop this pointer here,
  728. 1:13:49this connection. And what do we return?  What will you turn six, because again,
  729. 1:13:54six gets propagated through, and six is always  going to be P in this case. And so now I return.
  730. 1:14:02And I know six is P, but head dot next is four.
  731. 1:14:06And so I want for 2.23. So I say head dot next dot  next equals three. So I draw a pointer. And again,
  732. 1:14:14I dropped the cyclic dependency and I get rid of  this connection. And now I return, P is still six,
  733. 1:14:21I return to two and I say two's next value, which  is three, I want three dot next to point to two.
  734. 1:14:29So we do this, I dropped the pointer and I return.  And then finally we do this again. I dropped
  735. 1:14:35the pointer and now we're done. So let's let's  take the opportunity to look at the call stack
  736. 1:14:43and really understand how this is  working in a little bit more detail.
  737. 1:14:47In order to save some time I've written some  code. The first piece of code I want to look
  738. 1:14:52at is this idea of a node. Now this node  is just an abstraction that holds a value
  739. 1:14:59and a pointer to The next node. And so this is  the easiest way to build a singly linked list.
  740. 1:15:07Now, I also made a method called print link list.  And this will just print all the values. And this
  741. 1:15:12is a verification that we can use to ensure  that we've reversed the linked list correctly.
  742. 1:15:19And the final thing I want to look at is  the actual code. So this is the same code
  743. 1:15:23that was on the slide. And I want to look  at the call stack as we debug through this.
  744. 1:15:28So I have a singly linked list here 12345, just  as we looked at, and they're all linked together.
  745. 1:15:36And what we want to do is we want  to actually see how the reversal
  746. 1:15:39is working. So if I run and debug this,  we're going to look at the call stack.
  747. 1:15:46So the first thing I hit is this call. And you'll  notice in the stack frame, as I dive into this
  748. 1:15:51function, this is the initial stack frame that  gets put onto the call stack. And ask myself,
  749. 1:15:57is the node that I'm looking at, which is  one, is it No, and the answer is no. is the
  750. 1:16:02next value? No, the answer's no, it's  two, right. And we can see that here.
  751. 1:16:07So I continue through. And I just call reverse  list again, which is a recursive call. And as
  752. 1:16:13a dive into this, I add another stack frame to  the call stack. And now you see my values too.
  753. 1:16:20And my next note is still not  know. So I continue through.
  754. 1:16:25And I add another recursive call, which  adds another stack to the stack frame
  755. 1:16:30to the call stack. And I continue. And  this process is going to keep continuing.
  756. 1:16:37Now notice here, the value is five, but my next  value is actually null. And that satisfies this
  757. 1:16:44base case here. So if we continue, you notice  I just return null here. And this stops the
  758. 1:16:51recursion. So notice this stack frame should get  popped off. So as I continue through, that stack
  759. 1:16:57frame gets removed. And now I'm looking at four.  And it actually shows me what the result of this
  760. 1:17:04recursive call was for P. So as I step over,  we can look at p and see that P is just five.
  761. 1:17:12And so I'm basically saying the node dot  next, so let's evaluate that. So node dot next
  762. 1:17:20has a value of five. And I'm basically saying I  want five two point. So I want five dot next 2.2,
  763. 1:17:29whatever my current value is, which  is four, right? So we know no dot Val
  764. 1:17:37is four. So I'm changing the pointers  here. So as I step over, now I have four
  765. 1:17:46pointing to know as I execute this, so we step  over one more. So as I [email protected] dot next,
  766. 1:17:56there should be no which it is. And now the value  P which was five should be pointing to for now.
  767. 1:18:05So P dot next, dot Val is four. Right.  And so the whole idea here is that now
  768. 1:18:13we return the very last note again,  and we pop this off the call stack.
  769. 1:18:20And the node that we're looking at at this point  is three. And right now three is pointing to four,
  770. 1:18:28right? So if we look at no dot Val, which is  three, we can say no dot next dot Val, and this
  771. 1:18:34is four. But this is wrong, right? Because we  want for it to be pointing to three, not three
  772. 1:18:39pointing to four. And so that's what this line  is doing. I'm saying I want four to point back
  773. 1:18:44to three. And so I do that, and then I drop  the connection. And so now if I evaluate this,
  774. 1:18:52and we look at p, we can actually see the progress  that we've made five points to four, and four
  775. 1:18:58points to three. Okay, so we're not done yet. But  we're making progress. And as we return from here,
  776. 1:19:04we pop off the call stack. And we just keep doing  this process. We return we pop up the call stack,
  777. 1:19:11we come appear we return and we pop up the call  stack. And now we're back to the original call.
  778. 1:19:17And remember how we propagated the very  last value throughout all these calls. So
  779. 1:19:24as we look at the reverse value, reverse dot  Val, it's five and this is our new head node,
  780. 1:19:31which was the goal of this problem. And so if we  come in we look at reverse, we say okay, we have
  781. 1:19:37five the next value is for the next value. There's  three the next value, there's two and an x value.
  782. 1:19:43There's one, and then we're complete. And so this  is how the call stack works for a linked list
  783. 1:19:50reversal. A really fun problem to consider with  linked lists is how do you merge two sorted linked
  784. 1:19:54lists recursively. We're going to look at how the  call stack works for this Let's analyze this small
  785. 1:20:01snippet of code and see if we can conceptualize  what's happening on the call stack. We take in
  786. 1:20:06two head nodes. One was a list of values, and the  other is a singly linked list of values as well.
  787. 1:20:13And we asked this question, the first question  is, what's the smallest input I could pass in?
  788. 1:20:18And that's a good consideration for our base case,  the same formula that we've been applying. If I
  789. 1:20:24pass in a head node for a, that's no, then I can  just return b, because B would be merged with no,
  790. 1:20:33which would just be itself. And in the same  vein, if b is null, and I merge that with a,
  791. 1:20:38then I can just return a. And if they're both  No, then no merge with no is also just the null
  792. 1:20:46value. So this holds true. And that's the base  case. But let's consider the other side of things.
  793. 1:20:53And this is kind of a similar  comparison that we saw with merge sort.
  794. 1:20:57Right now we're considering, Okay, first of  all, base case. But second of all, what's the
  795. 1:21:04unit of work that we need to do the recursion.  And I think it'll help if we look at two lists.
  796. 1:21:11So we have two sorted lists of values. And  we want to go through this recursive call.
  797. 1:21:17And the first thing that we do is we  add a stack frame to the call stack.
  798. 1:21:22And it basically says, the head nodes that I have  are one and four, so neither of them are null.
  799. 1:21:29And what I want to do is I want to say,  all right, which which value is smaller,
  800. 1:21:34the value in the first node or the  value in the second node A or B,
  801. 1:21:39A's data is one and B's data is four. So that  first conditional is the one that will be
  802. 1:21:44recursive upon. And I basically say one dot next  is going to be pointing to sorted sorted merge.
  803. 1:21:51And node A is going to be incremented by one  value, so I add another stacker into the call
  804. 1:21:56stack. And so now I pass in eight. And now he is  getting compared with four. And we still haven't
  805. 1:22:05hit our base case, right. And so I look at the  L statement. And I compare eight and four and
  806. 1:22:10four is obviously smaller. And so this is  going to be the next node that I consider.
  807. 1:22:16And I just continue this process.  So now eight and 11 gets compared,
  808. 1:22:20instance eight is smaller, and I pass in 22. And  I add another stack frame. And now 22, and 11,
  809. 1:22:27get compared. And since 11 is smaller, I pass in  the node right after 11. And these get compared.
  810. 1:22:35And notice in the calls, the recursion needs to  complete, but I'm actually setting these results
  811. 1:22:41as the next value. And so as we compare  22 and 1616 is smaller, so I pass in 20.
  812. 1:22:51And now 20 is smaller. But it's interesting  because there is no node after 20.
  813. 1:22:57And so now I'm at this point where I have  no. And now we consider the base case,
  814. 1:23:03if we have a null value, which is B in this case,  then I just returned a. And if I return a then I'm
  815. 1:23:08just returning the reference to the node of 22. So  here's the base case. And what I can do is I can
  816. 1:23:17just simply return 22 here, and  this gets popped off the call stack.
  817. 1:23:22And this is where the unravelling starts  to begin. So when I return 22 here,
  818. 1:23:30I actually change 20s dot next pointer, right,  because I'm saying 20 dot next, is equal the
  819. 1:23:37result of sorted merge. And since sorted, merge  returned 22, I can now modify this pointer.
  820. 1:23:46And from here, I return a. And when  I return a I'm just returning 20.
  821. 1:23:51And this gets popped off the call stack.
  822. 1:23:55And now, you know we're looking at 16. And we're  saying what should sixteens next pointer be? Well,
  823. 1:24:0216 should point to 20. But it already is pointing  to 20. So we don't really need to do anything.
  824. 1:24:07And so I returned from here 16. And  this gets popped off the call stack.
  825. 1:24:12And again in the same in the same case,  11 is already pointing to 16. So we don't
  826. 1:24:17need to modify anything. But we return 11 from  here and this gets popped off the call stack.
  827. 1:24:24And now we have this question eight was pointing  to 22. But since we returned to 11, we can now
  828. 1:24:30say that eight is going to point to 11. And that  means we modify the pointer, which we've done.
  829. 1:24:37Right, and now we return eight from  this stack frame, a smaller value,
  830. 1:24:43and this gets popped off the call stack.  Now we compare eight and four four was
  831. 1:24:48pointing to 11. But since eight got  returned from the recursive call,
  832. 1:24:52and we take four and we point it to eight now we  return the smaller value From the stack frame.
  833. 1:25:03And what do we do here? Well, what happens  here is we pop this off the call stack,
  834. 1:25:09and four is the smaller value. And so now what  we want is we want one to point to the four node.
  835. 1:25:17And this is our final stack frame. And from  here, we just return one, which is the head node,
  836. 1:25:24which is the smallest value from the two sorted  lists. And from here, this gets popped off the
  837. 1:25:30stack frame. And if we follow these edges, or  these pointers, we noticed one goes to four goes
  838. 1:25:37to eight goes to 11, to 16, to 20, to 22, to  40. And this ends up being our sorted list.
  839. 1:25:45And even for this, we can take a second to  look at the code and understand the call stack.
  840. 1:25:53Alright, so I've gone ahead and I've actually  built a couple linkless. So we have one here.
  841. 1:26:00So one, 513 14, and 550. And we have another  completely independent link lists to 15 130
  842. 1:26:07203 50. And the idea here is how do we take  these and merge them in their sorted order.
  843. 1:26:15And so we take the same code  that we were just looking at,
  844. 1:26:19but now we want to analyze it from the call stacks  perspective. And so I'm going to come over here,
  845. 1:26:24and I'm going to put a breakpoint on the initial  call. And we're going to debug into this function.
  846. 1:26:35So as I dive into this function, you'll notice  the call stack grows. And now we have two lists,
  847. 1:26:40this would be a and b from the example. So we  have one and two. And this corresponds to the
  848. 1:26:45two initial nodes that we have here, two and one.  And what we're doing is we're checking if you know
  849. 1:26:54the first list is null, then we return the second  list. And if the second list is null, we return
  850. 1:26:59the first list. This, of course, does not hold  true yet. So we continue and compare the values.
  851. 1:27:05Since one is less than two, we jump in here  and we do another recursive call. And notice
  852. 1:27:12that we pass in the next value that one  was pointing to, but two stays the same.
  853. 1:27:17And we continue this comparison. Now since five is  greater than two, we go to the second conditional,
  854. 1:27:23and we do a recursive call. And notice  the stack frames are just growing.
  855. 1:27:28They're just growing. And this stack frame is  dependent on the result of this stack frame.
  856. 1:27:33And as I continue through and do these  comparisons, I have another stack frame.
  857. 1:27:38And each subsequent stack frame has a dependency  on the results of whichever one is above it.
  858. 1:27:45And so we keep doing this. And we do this  until we hit one of these base cases.
  859. 1:27:50So I continue through. And  now I've hit a base case,
  860. 1:27:54the value that we have is list one, which is 550.  And I'm just going to return this. And when I
  861. 1:28:00return it, what happens? Well, it's the result of  this function. So I say l to Next is 550. And when
  862. 1:28:08that gets evaluated, I just return here. And you  notice that the stack frames will start shrinking
  863. 1:28:12now because we've hit a base case. And so as I  continue through, and I'm returning these values,
  864. 1:28:19you'll notice that our call stack is shrinking in  size. And we're basically reassigning the pointers
  865. 1:28:24to the next node. And it shrinks and shrinks and  shrinks. And now finally, we get to a point where
  866. 1:28:32the sorted operation is complete. And so if we  look at sorted merge, if we look at this value,
  867. 1:28:37we noticed that the first value is one, the next  value is two 513. And it's now in sorted order. So
  868. 1:28:46we've taken two sorted lists and merge them in  ascending order, which is the expected output.
  869. 1:28:53And this is just an example of taking two linked  lists and showing how the merge operation works
  870. 1:28:59on the call stack. This brings me to one  of my favorite topics, which is trees.
  871. 1:29:06And trees are one of the really fantastic data  structures that work really well with recursion.
  872. 1:29:12And the first question we're going to look at  is inserting a value into a binary search tree.
  873. 1:29:17Now a tree for especially a binary search  tree is going to be a series of nodes and
  874. 1:29:22we're going to basically start from the top  down, we're gonna have a bunch of connections,
  875. 1:29:27and the number of children at most that  we can have from a single notice to
  876. 1:29:32and the least amount of nodes you can have  is zero. And so what we're going to do is
  877. 1:29:35we're going to add a bunch of numbers here  and analyze the properties of this tree.
  878. 1:29:41Notice that everything to the left of the root  node 100 is less than 100. And everything to the
  879. 1:29:47right is greater than 100. Now this property it  turns out holds true recursively. So if we look
  880. 1:29:54at at everything to the left is less than 80.  Everything to the right is greater than eight
  881. 1:30:01But it's interesting because everything  to the right is greater than 80,
  882. 1:30:05but also still less than 100. And so if you  look, the largest value on the subtree of 80,
  883. 1:30:13on the right subtree is 95. And 95 is still less  than 100. And so if you're not familiar with the
  884. 1:30:19properties of binary search trees, I encourage  you to just do a quick, quick look and look at
  885. 1:30:25the rules for that. But this is pretty much  the entire rule set for a binary search tree.
  886. 1:30:30And we're faced with this task and the task  is to add this node, and the node is 108.
  887. 1:30:37And if we look at this value, we basically say,  Okay, I'm going to start at the top of this tree,
  888. 1:30:41and I'm going to figure out where can I place 108.  And I compare I say, is 108, greater than 100,
  889. 1:30:47or less than 100? or equal to 100? And I  say it's greater than, so I go to the right.
  890. 1:30:53And I ask is 108 greater than 120, or  less than less than or equal to 120?
  891. 1:31:00And we see it's less than so I go to  the left sub half. And I asked the
  892. 1:31:04same question is 100, a greater than 110 or  less than 110. And I see it the less than,
  893. 1:31:08and so now I go down here. And I draw connection.  And this is the point where 108 belongs.
  894. 1:31:16And here you can see it's its sale still satisfies  the rule set, right? 108 is greater than 100.
  895. 1:31:22So it satisfies that property, it's less than  120. It's less than 110. So this is the valid
  896. 1:31:28position for 108. And so let's look at the code.  Turns out the code for this is extremely simple.
  897. 1:31:38And like I had said, before we consider the base  case, what's the smallest thing I can pass in?
  898. 1:31:44Well, what if there is no root? Right? What if  this is the first value that we're inserting
  899. 1:31:48into the tree? Well, if that's the case,  then what I do is I just create a new node,
  900. 1:31:54I set the data, and I return that value. Right?  And it turns out, that's a fairly good base case.
  901. 1:32:02Now, if we consider the other case, now we  need to start thinking about, okay, well,
  902. 1:32:08what if I need to do some comparisons? And this  is where I start comparing the data. So let's look
  903. 1:32:13at what's happening. The first  question says, If I hit null,
  904. 1:32:17I've recurse to my end goal, according to the  search tree properties of the binary search tree.
  905. 1:32:24Now remember, binary search tree node, insertions  will always happen at the leaf level. So they
  906. 1:32:30always happen at the end. So if you've done  all your comparisons until the very end,
  907. 1:32:35then you've hit a valid position to insert  the data. Right? And that valid position
  908. 1:32:39would be when you have no more comparisons to  do, which is implied by the head node B, no.
  909. 1:32:47Now the next conditional I look at is  basically saying, Okay, I'm at a node,
  910. 1:32:52and I want to compare it to my current node.  So remember, when we were comparing 108 to 100,
  911. 1:32:57I asked this question I say, is the node i want  to insert greater or less than this value. If it's
  912. 1:33:03greater than I say, head dot right equals, and  then I just make another recursive call. And I
  913. 1:33:10progress to either the left or right half of the  subtree. The other side is just the opposite. So
  914. 1:33:18if I'm less than the current node, then I want  to go down the left hand side of the sub tree.
  915. 1:33:25Now, this final piece right here, is, once I've  done all this work, I've done all the insertions,
  916. 1:33:31I just want to return the original root node.
  917. 1:33:34And that original root node just says basically,  here's the tree that you started with.
  918. 1:33:41Let's look at the call stack.  During this insertion process.
  919. 1:33:48I call insert node. And I'm comparing 100 and  108. And I know 108 is greater than 100. So I
  920. 1:33:54recurse down the right hand side. Now when I get  to this point, I add another stack frame. And I'm
  921. 1:34:00comparing 120 and 108. And I know 108 is now less  than this. So I recurse down the left hand side.
  922. 1:34:09Now here, I'm comparing 108 and 110. Add  another stack frame, I compare these values.
  923. 1:34:16And because it's less than  I go to the left hand side.
  924. 1:34:19And this is interesting, because now  my value that I compare against is no.
  925. 1:34:25And remember, when we look at the code here, no is  just the base case. This is what we consider when
  926. 1:34:31we insert a node. And so I just create that  108 node and I return it right. And so here
  927. 1:34:38is where I would say, okay, new node had died  data equals 108. And I would return this node.
  928. 1:34:45And so from this stack frame, I would actually  return the 108 node and pop this off the stack.
  929. 1:34:52And what I would do here because I was saying  110 dot left, equals whatever that value was.
  930. 1:34:59Now I can draw connection to 108 and just  start returning those values up the stack.
  931. 1:35:06And that's the process of inserting  a value into a binary search tree.
  932. 1:35:12Let's look at another fun problem, which is also  a kind of a fun depth first tree reversal problem.
  933. 1:35:20And the purpose here is to print all the leaf  nodes. So we're looking at the same tree.
  934. 1:35:25But we want to build a function  that prints out all the nodes
  935. 1:35:28that we see here in the order from left  to right. So 30 6080 590-510-8115 and 150.
  936. 1:35:39Now if we think about the recursion for how this  works, mechanically, we start at the root node,
  937. 1:35:44and we go all the way down. And we hit a  leaf node here, so we would print it out.
  938. 1:35:50And what I would do is I would basically  pop off the call stack and go to this node.
  939. 1:35:55But I would immediately go to this nodes, right  subtree. And this would be another leaf node
  940. 1:35:59because it has no children. And I would print  this and recurse up the stack. And then I would
  941. 1:36:04go back up to 80. And now go down, it's right  half. And here, I'll go down its left sub half,
  942. 1:36:11find a leaf node return, go down the right sub  half, find a leaf node return. And this process
  943. 1:36:16just continues. And the order of execution is  really important to understand here. Remember,
  944. 1:36:22if I'm going down the left sub half, I can't  even consider the right sub half until the left
  945. 1:36:27sub half has been fully explored. And that's  part of the property that depth first search
  946. 1:36:33follows. And we'll look at another DFS example  with a graph in just a second. So now I go down,
  947. 1:36:39I recurse down the left sub half, I find the leaf  node I print it, I recurse back up and do this
  948. 1:36:45all the way until I'm basically out of leaf nodes  to find which brings us here. And now we're done.
  949. 1:36:56So here's the code for this. And  we'll look at this to understand
  950. 1:37:00how things get added on the call stack as  well in the code. But again, our base cases,
  951. 1:37:07what happens if we pass in a null value,  that's a pretty good base case, right?
  952. 1:37:11Because then we would just return there's  nothing to print if you just pass a null.
  953. 1:37:17But we also have to think about the  goal, the goal is to print the leaf node.
  954. 1:37:22And so we want to keep recursing  until we hit a leaf node.
  955. 1:37:26And the properties of a node being a leaf node is  that there are no children. And so this is where
  956. 1:37:32we start to ask, okay, if there are no children  to the left, there are no children to the right,
  957. 1:37:37then I can actually evaluate the underlying  value of that node and just return.
  958. 1:37:43Now, if we're not a leaf node, then we need  to recurse down the left sub half of the tree,
  959. 1:37:49which is what we're doing here. And if  we have a right sub half to recurse,
  960. 1:37:56then we go down the right sub half after the  left sub half has been explored. So this allows
  961. 1:38:02us again, this is why we evaluate the left hand  side first. And then we go down the right hand
  962. 1:38:07side recursively. And we do that for all recursive  sub trees up until we're done. So let's look at
  963. 1:38:13the call stack and see how this is executing,  or we're looking at here is the same program. So
  964. 1:38:20we have some code here, print leaves, and it's  the same code that we looked at before. So if
  965. 1:38:26the input root node is null, we just return, we  evaluate the left and right. And this basically
  966. 1:38:33checks if a given node is a belief. And we print  it, and then we pop ourselves off the call stack.
  967. 1:38:43Now if we don't satisfy that, then we go  down the left and right half the subtree.
  968. 1:38:48So using the same code that we looked  at for insertion, I've actually built
  969. 1:38:51up a tree to use. So I have some input with  a bunch of numbers. These are all the numbers
  970. 1:38:58that we were looking at before I believe. And it's  just a random set of numbers. So nothing special.
  971. 1:39:03And we just insert them. So I have a function here  from the previous example called insert node. And
  972. 1:39:08it just builds us a tree for us. And so now what I  want to do is I want to print all the leaves here.
  973. 1:39:16So what I'm going to do is I'm going to add a  breakpoint here. And we're going to debug straight
  974. 1:39:20into it. And the first question I asked is, is the  root node? No. And the answer is no, it's not No.
  975. 1:39:27Right? So this first value. And so what do I do?  Well, I recurse down the entire left half of the
  976. 1:39:34subtree. So remember, it starts at 100. And now  I go down to the left. And since it's not No,
  977. 1:39:40I skip over this. And now I just recurse down the  left half of the tree. And for that node, again,
  978. 1:39:47it's not know. And for that specific node i again  recurse down to the left sub half of the tree.
  979. 1:39:54And I just keep going down the left  half I don't even explore the right half
  980. 1:39:59until this level. Tap has been fully explored.  And you notice we just printed out the first
  981. 1:40:04leaf node, which is 30. And it's only until I  explored that entire sub trees left half, that I
  982. 1:40:11can now go to this other sub trees right half. So  I dive in here. And now I go down the right half.
  983. 1:40:18It turns out that the right half again,  is just another leaf node. And so I can
  984. 1:40:23just print this value. And if I put a breakpoint  here, you'll notice that we just keep printing.
  985. 1:40:31You know, we hit Knowles, we hit  Knowles, and we print the leaves.
  986. 1:40:35And you can see down here, this is the  leaf node evaluation that we're doing.
  987. 1:40:40And the reason I wanted to walk through  this animation is just further emphasize
  988. 1:40:44that when we're thinking about the call stack, I  cannot even consider evaluating this expression
  989. 1:40:49until every recursive call, and every  sub call has been fully evaluated.
  990. 1:40:56Once that holds true, then I can start unraveling  the right sub trees and I work from the bottom up.
  991. 1:41:02And that's the idea for these kind of  DFS traversals that we're dealing with.
  992. 1:41:09This brings us to our last section of algorithms  that we'll be looking at. And we'll look at a
  993. 1:41:14simple example with graphs. And we'll just see  how similar working with graphs is to something
  994. 1:41:19like trees. And we're going to look at a very  popular algorithm known as depth first search.
  995. 1:41:25And the idea is that we start at a node like a,  and we basically say we're searching for a node,
  996. 1:41:30let's say the node we're searching for is H.  And so I start from a and I say, Okay, let me
  997. 1:41:34get all the neighbors and see is this my value,  so a is not my value. So I go to my neighbor,
  998. 1:41:40and I say B, B is not my value, go to C, D is not  my value, but I get all my neighbors. And this is
  999. 1:41:48at an interesting point, because I'm searching  for H, right, which is in the far half. But when
  1000. 1:41:54I get my neighbor's, let's say the first neighbor  I get is E. And so I actually have to explore down
  1001. 1:41:59the depths of E before I even consider going to H.  And so I go down, and I see E. And now I go to F.
  1002. 1:42:07And now I have to look at all of F's  neighbors. So the first neighbor I go to is K.
  1003. 1:42:12And if k had neighbors, I would  explore all of those neighbors.
  1004. 1:42:15But it doesn't, so I can just pop off. And now I  go to J and back to F. Now I go to I am back to F,
  1005. 1:42:24and I kind of recurse backup the call stack.  So now that I've explored all the neighbors of
  1006. 1:42:31right, I've explored one neighbor of D, I  only have one unvisited neighbor left for
  1007. 1:42:37me to explore. And it's g so I go to G and I  say is this the value that I'm looking for?
  1008. 1:42:42And it's not. So I look at the neighbors of G  and I say is this the value I'm looking for?
  1009. 1:42:46And it is. And this is the idea of depth first  search. And so we're going to look at a piece
  1010. 1:42:52of code and walk through it line by line and see  how something like this would work recursively.
  1011. 1:42:58We look at a piece of code like  this, and we have the input node.
  1012. 1:43:02And to avoid cycles, which can happen in graphs,  which is different than trees will keep a set
  1013. 1:43:09of visited values. So we never want to visit the  same node more than once. And so that's what the
  1014. 1:43:14visited set is going to do. And the goal integer  here is just the node that we're looking for.
  1015. 1:43:20So I know the graph we were just looking at was  letters, but we're going to assume that we're
  1016. 1:43:25working with a graph with integer values. And we  ask our base case questions again. So think about
  1017. 1:43:33the smallest thing you could pass in if you pass  in an empty node, a normal node, then there's no
  1018. 1:43:39way you could ever find the goal, because the  goal is an integer value. And so here, we would
  1019. 1:43:45just return false. So this is a base case. The  other base case is if we've actually found the
  1020. 1:43:51node. So let's assume the node you pass in is the  gold node, then here we found the node. And so we
  1021. 1:43:57would just return true here, and this would imply  that we found the value that we're looking for.
  1022. 1:44:02But let's assume that we haven't found the  value, just like the example. The first thing
  1023. 1:44:07that we need to do is we need to aggregate all  of the neighbors from a given node. And so we're
  1024. 1:44:12going to assume the node API has a function call  that allows you to aggregate all the neighbors.
  1025. 1:44:19And the first question we ask is have  we have we visited this node before?
  1026. 1:44:24Remember, we want to avoid cycles. And so we  never want to visit a node more than once.
  1027. 1:44:28And so if it's been visited, we just ignore it  and we continue. But if it hasn't been visited,
  1028. 1:44:34then we add it to the visited set. And we start  the exploration. And we ask this question,
  1029. 1:44:40have we found the node that we've been working  with with this particular neighbor. If we've
  1030. 1:44:46found the solution, then we can just return  right away. Now if we haven't found the solution
  1031. 1:44:54from this particular neighbor, then we'll continue  recursing through the rest of the neighbors
  1032. 1:45:00And it's at that point, if we found it, we would  return true. And this brings us to our last base
  1033. 1:45:06case, let's assume that we've traversed all of  the neighbors. And we never found the solution.
  1034. 1:45:13That would mean that the goal value that we're  looking for was just never in the graph to begin
  1035. 1:45:18with. And this brings us to our final base case  of just returning false. And this is a way to say,
  1036. 1:45:23hey, I've searched for this node, but it  didn't exist in the graph. It's false,
  1037. 1:45:29it did not show up in the search. And  that's where we can terminate the algorithm.
  1038. 1:45:36I think it's important to spend just a little  bit of time talking about optimizations that
  1039. 1:45:40you can make when using recursion. And  the first that we're going to look at is
  1040. 1:45:44memoir, zation. And caching, which we  talked about briefly when writing Fibonacci.
  1041. 1:45:49And the question that we're trying to answer here  is how can we speed up our program by pretty much
  1042. 1:45:54storing things that we've already re computed or  computed for the first time. So if I've computed
  1043. 1:46:00some very expensive operation, and I have to re  compute it again, in a subsequent recursive call,
  1044. 1:46:06I want to check to see if I've done  that work already. And if I have,
  1045. 1:46:09I'm just going to return the result instead  of re computing that operation again.
  1046. 1:46:14And the best way to kind of conceptualize This  is looking at the Fibonacci tree sequence.
  1047. 1:46:19Here, we see that I'm doing F of three twice,  and I'm re computing F of two, three times.
  1048. 1:46:24And the question is, why can't I just compute  f of two once, and then just reuse that value,
  1049. 1:46:30so I don't grow these sub trees. And it  turns out, you can do that really easily.
  1050. 1:46:35Let's look at the modified Fibonacci code. Notice  I have a hashmap here and hashmaps have this
  1051. 1:46:41property, such that we can retrieve values from  the map in constant time access, so big O of one.
  1052. 1:46:48And the reason is because the data that we  put into the hashmap is hashed. And so we have
  1053. 1:46:52constant time access to the memory addresses  in which that data is stored. And so when I
  1054. 1:46:58call the Fibonacci function, the first thing  I always do is I say, Hey, does the cache have
  1055. 1:47:03this value. And you can also notice that I've pre  populated the cache with the base cases as well.
  1056. 1:47:12Now in the instance, that the cache does not have  the value, I still go about the recursive call,
  1057. 1:47:18as I had done previously. But I ensure that  once that recursive call has been evaluated,
  1058. 1:47:23I put the result into the cache.  And then I return my result.
  1059. 1:47:28And this is a really key component because  the cache is global, right. And so all the
  1060. 1:47:33subsequent recursive calls are putting their  results in the cache. And as things get added,
  1061. 1:47:39and popped off the stack, I can just keep  cross referencing the cache to see if I'm
  1062. 1:47:43doing redundant work or not. And that saves me  a lot of computational power. So that's the idea
  1063. 1:47:49of memorization and caching. And you can do a  lot of this in a lot of different situations.
  1064. 1:47:59The next optimization I want to just discuss is  this idea of tail call recursion optimization. And
  1065. 1:48:06people usually refer to this as you know,  tail call optimization, or tail recursion.
  1066. 1:48:11And the idea here is that basically, the compiler  in certain languages, especially functional
  1067. 1:48:16programming languages, will optimize the number of  stack frames that get added to the call stack to
  1068. 1:48:23basically remove this idea of stack  overflows in a lot of scenarios.
  1069. 1:48:28Now, the way that the compiler does this analysis  is it really looks at the last function call.
  1070. 1:48:34And it has to be a recursive call. And we'll  look at an idea of comparing these two things.
  1071. 1:48:41And there was actually a response on the  computer science Stack Exchange by this user,
  1072. 1:48:46and he gave two examples. The first is  this simple recursive factorial function.
  1073. 1:48:53And notice that the final return value is a  value multiplied by a recursive call. Now,
  1074. 1:49:00this is not tail recursive, because the return  value is not just the recursive call. And so in
  1075. 1:49:08order for this to be tail recursive optimized,  we need to modify to look something like this.
  1076. 1:49:15Now, if you spend a second to look at this  code, you'll realize that functionally it's
  1077. 1:49:18doing the exact same thing. But we've had  to modify the parameters to get to a point
  1078. 1:49:24where we can build a function that's tail call  optimized. And the reason it's optimized with this
  1079. 1:49:31tail call recursive property is because the final  return value is in itself just a recursive call.
  1080. 1:49:38And it turns out in certain operating in certain  languages, in their compilers, they've implemented
  1081. 1:49:45some optimization techniques to exploit  this property and reduce stack overflows.
  1082. 1:49:52Now, I just want to make a couple notes  on this. The rule of thumb is to always
  1083. 1:49:56make your recursive calls be the last instruction  Nothing gets added to it, just the recursive call.
  1084. 1:50:04And the other thing to consider is that this is  supported mostly by functional languages. But it's
  1085. 1:50:10not inherent in languages like Python and Java and  even JavaScript. There are certain browsers for
  1086. 1:50:19ies 2016. They do support tail call recursion  optimization for things like JavaScript,
  1087. 1:50:25but it's not supported across the board. And so  as you're considering this optimization technique,
  1088. 1:50:32think about what language you're using. And think  about, you know, the compiler that you're using
  1089. 1:50:36and where your code is being executed to see if  this sort of optimization is supported or not.
  1090. 1:50:46So this brings us to our final slide. What's  next? And when we ask this question, it's really
  1091. 1:50:51to consider like how far do you want to go in  your journey of recursion. And that brings us to
  1092. 1:50:56another video that we'll have coming out, known  as algorithmic mental models for backtracking.
  1093. 1:51:02And this is the entire notion that we basically  solve even more complex problems by looking at a
  1094. 1:51:08decision space and recursively going through  and seeing if our decision was good or bad,
  1095. 1:51:13and backtracking to see if we can  solve the solution in a different way.
  1096. 1:51:17And that's the next phase that I think  is important for understanding recursion.
  1097. 1:51:21So thank you guys so much for watching. Be sure  to check out my channel youtube.com slash the
  1098. 1:51:25symbol engineer and connect with me on youtube  or Twitter or LinkedIn. And I'd be happy to talk
  1099. 1:51:31with you and answer any questions you may have.  So thanks, guys for watching and have a good day.

About this transcript

This page contains the full transcript of Recursion in Programming - Full Course by freeCodeCamp.org, generated from the public captions YouTube serves with the video. The transcript has 19,459 words across 1,099 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.

What you can do with it

Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.

Free YouTube transcript tool

YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.