 Alright guys, today we're going to go over another spodge question called edit distance. So you're basically given two strings A and B and you want to know what is the smallest number of operations you need to transform A to B. So the number of operations, the different type of operations you could do is delete one character from one of the strings, insert one character into one of the strings, or replace one character from one of the strings with another character. So you input same as you have number of test cases. For each test case you have string A string B both strings will contain only uppercase characters and they won't be longer than 2000 characters. Okay, so this problem is a little bit difficult to understand, but it's actually very tricky. This is actually a very, very tricky problem. But then you have to use dynamic programming, but the best case to do this first is actually to figure out the recurrence relation, then build your DP table from there on. So we're going to actually just talk about how to do your recurrence relation first and then we'll talk about how to create your DP table. Alright guys, so first of all, back to the problem. Okay, so we have two strings A and B and there's three operations we could do. One we could add a character, so add a character. So add from one character to another. From one of these characters we could add a new character, right? Another one we could delete a character. And the third one we could replace a character. So these are the three operations, right? Add a character, delete a character, replace a character. And we want to find the minimum number of operations it takes using these, any of these to get from A to B, right? From transform A to B. Okay, okay, so for starters, how do you get started with this? First of all, let's think about the most basic, basic case possible. And what is that basic, most basic case possible, okay? Let's say we have all the, let's say our string A is all empty. So let's say A is all empty. So this is empty string and then B is money, right? This is the first case. If A is empty and B is all empty, if A is empty, B is all money. What can I do here? Well think about this. The only way I could get to A to B is to add all the characters from B to A, right? At all the characters, the only possible ways to add all the characters here, right? So how many number of characters can I add? Well, it will just be the size of B, right? Because I have to add all the characters from B to A. So in this case, it would have to be the size of B. So it would be one, two, three, four, five, it would be the length of five, five characters I have to add, right? So if A is a length of M, right? And then B is a length of N, right? So let's say the string size A is a length, like have number of characters M and B is length of N, then in order to transform A to B, if A is empty, I would have to add all characters of N, right? All characters of N. That's all the characters of B from B string into A. So that's one thing you would have to think about, right? We would have to return N, the number of characters of B, okay? All right, now let's think about another really easy case. This time the second case, very easy case, is what if B is empty? So in this case, let's say A is food, this is A, and then B is empty, right? So how do I get from A to B? Well, simple, you have to delete all the characters of A, right? So all the characters of A has to be gone, right? We have to delete all the characters of A. So what is the number of operations I have to do here? Well, if A is a length of M and B is a length of N, I have to delete all the characters of A to transform A to B, right? To transform all these strings of food into empty string of B. So to delete all the characters of A, I would have to return, number of operations I would have to do was just return the number of characters of A, right, because that would be the number of times I delete the characters for A to transform A to an empty string of B. So yeah, those are the first two base cases, okay? So that's the first starting base case. All right, now let's go to the next base case. So you guys think about this, so we already have the two base cases, and that's basically, we have the two base cases, okay? So that's, we're making some progress here, okay? So now, let's think about something. Let's say I'm at the current character, right? And it's actually equal to each other. Let's say the character's actually equal. So let's say I'm looping through the string A and B, and I'm at this current character O. And this O is equal to this, this O, right? Both of these in food and money, both of these characters, O, O are equal together. So, since they're equal, what do I have to do? I don't have to do anything. Think about it. I don't need to add a character. I don't need to delete a character. I don't need to replace a character. They're equal. So what do I do? I move on to the next character. So if the length of string A is M, right? And length of string B is N, I just move on to the next character. So I just recursively call my function that's supposed to find the minimum possible value to basically go for both of these strings, right? Minimum number of operations. I just go to the next character. So what does that mean? So if my current character is, if I'm at the last character of M, right, of A, I just subtract one, right? So I'm gonna subtract one from both of these and recursively call them. Recursively call to the next character, right? So if I'm currently at the current character of M, let's say I'm at the current character of M, right? I just recursively call it. So I go just go to the next character, right? So if, yeah, if I was at the last character of M and a last character for B, I'm at the last character. So for N, so like, let's say I was at both of these ends and let's say they were the same, well, then I just go on to the next character, right? So I just go on to here. Assuming I'm going through like, going, looping through backwards, right? So I would just go to the next character. Recursively call and just go to the next character and then check, so on and so forth. So that's assuming like both of these are actually the same character. So let's just say D is actually the same character. So let's say this was like Y, yeah. Yeah, then if there was Foy and Money, then I just go to the next character, okay? So that's why we subtract one, recursively call and subtract one from our current character index that we are at and we'll go to the next character, okay? So yeah, all right, now let's think about this. Now this is much more difficult to understand, okay? So here now at this point, we actually have to find for each of these characters, right? For each of these operations. So what do I do? Let's say they're not the same, okay? So first of all, let's say I'm adding a character. So for food, right? Let's say food and money. Yeah, okay, so let's say I'm at D and E, right? And they're not the same, right? They're not the same. But I want to make them the same, right? I want to make them the same. So if I want to transform A to B, I would have to let's say I want to transform A to B, right? And for A, I don't have enough characters for A, right? So I'm gonna add a new character. Well, how many new characters can I add, right? How many new characters can I add, right? I'll just add the number of characters I could add is just one, right? Because I'm just adding one every time, right? I'm just adding a new character for each of the, for each adding a character for an operation, for one operation of adding a character. I just add one, right? So if I want to change food to money, right? Change this to an E. I just have to add a character like before it. So I'll just change this food to become like, make this E and the D shifts to the right, right? So here now the two, the two characters from food is now foie and money, right? So these, this character E and this character E is the same. Now, okay? So then what is the number of operations I just did? I just added one. It was just one new character, right? Because I was a new character. But now let's say I want to delete this character. So let's say, yeah, this is kind of a little difficult, but here in this case, food has like fewer characters than B than money. But let's say I want to delete a character, right? Let's say it's like, I don't want to deal with this, right? So let's say, let's say there was like a Z here. Yeah, foods, right? And I don't like this character Z. And Z and Y are not the same, right? Z and Y are not the same. Well, what could I do? Okay, assuming that the next character Y, so let's say I have another character Y here at A, let's say, assuming the next character is the same, like the last character Y is Y equal to Y. So if I delete a character of Z, the next character is gonna be equal so that I don't have to do anything. So what is the number of operations I take to delete a character, right? Well, if I'm just deleting a character, I just delete Z, right? So what is the number of operations I just did? Well, I just deleted one character, so I just did one operation, right? You guys seeing what I'm doing here? You guys seeing understand what I'm doing here? I just deleted a character, so it's just one operation, right? And let's talk about replacing. So let's say I'm back at Z, right? I'm at Z, right? And now Z and Y are not the same, right? Z and Y are not the same. But I wanna replace this character, right? I'm gonna replace Z to become Y. So here's Z, I'm gonna change this to become Y. Well, what is the number of operations I just did here? To replace this character to Z to become Y, right? To make A and to B. What's simple, it's just one. All I did was just one operation because I just had to remove one character from Z to make it become the same character as Y for B, for A and B. So for all of these, every single one of these, you're gonna do one operation, right? You're gonna do one operation. So for all of these, what we have to do is we just do one operation for all of these, okay? Now, so we just do one operation for all of these and we recursively call each of these and what we need to do is call the minimum and recursively call, our function is going to spit back out the minimum number of operations to do, right? Our function is gonna spit back out the minimum operations to do and that function is going to, every time, one week of recursively call, it's gonna do one operation. So it's gonna do plus one. It's gonna have like a count of plus one, right? And each time we do this, we're gonna go to the next character and then do the minimum of each of these operations of adding a character, deleting a character and replacing a character, okay? So let's go back to adding a character, right? All right, so remember this. If I add a character, right? Let's go back to this adding a character. So at this point, what we're trying to do is we know that each of these operations take one, does one operation, right? So if I want to basically get to add a new character, how do I recursively call this function, right? Let's say I want to add a character, right? I know that my minimum operations I want to do, right? I need to do a minimum of adding a character, deleting a character and replacing it. And I know that each of these takes one operation, right? I'm knowing, I understand. So I need to recursively call this function that I'm gonna have over and over again, right? And each time I'm going to take the current index that I'm currently on for the each character. So I'm gonna have an index of this one and this one, right? Let's say we're starting from the back. Then I'm gonna go next and go next, right? If we, remember, if they are the same character, so O and O, then I just move both of them down, right? I just recursively call on the next character to change it, right? Because at this point, if they're the same, then I just go to the next character, right? So let's go back to the last character. We have Z and Y, okay? If I wanna add a character, so I'm gonna add Y. What am I recursively calling? Like, how am I changing my value for A and B, right? My indexes, so let's say I'm at my index for Z. Remember, we're starting at the last index, right? So for A, if the length of our string A is M and the length of our string B is N, we're starting at like M minus one and N minus one, right? So, okay, so if I want to add a character, what do I do? Well, simple, if I added a character, I'm gonna recursively call, I move my B pointer down. So yeah, so if I'm adding a character, let's say I wanna add Y. What I'm gonna do, let's say I added the character already. We added the character Y, right? So like, I added a character Y. So if I added a character here, so like for Z, let's say I changed this and I added a character to Z. Or actually, it's much better if the Z was not there, so hold up. Yeah, so let's say we have food, right? And I wanna add a character. So in this case, I'm gonna add a character Y. So if I add a character Y, I'm gonna add a character Y now. Now, what does this mean? After I added this character Y, what does this mean? It means both my values of A and B are equal now, right? It means they're equal, right? So if the values of A and B are equal, but I don't know if the next character for A is equal or not, right? D, I don't know if I'm gonna add another character for D, another character Y again for D. So what do I do? I'm going to move the right pointer of my string of B down by one. Okay, so at this point, after I added a character Y, I'm gonna move my string of my pointer of B down by one. And why do I do this? The reason why I do this is because I know that I already added a character for A already, right? And I know that this character is the same thing as this character for B. But what I don't know is that do I need to add another character, right? I don't know if I'm gonna add another character or not. But I do know is that the current character of B is in the right position, right? The current index of B, the pointer of B that I've said it at is in the right position. So I could just move on to the next value for B, okay? Right, you guys get what I'm saying? I don't know if my next character of A is in the right position or is the right character or not. Like I could keep adding. I might need to add another character. Like, let's say, I don't know. Let's say this was, let's say we had fewer characters, even fewer. So let's say this was, okay, even better, right? So let's say we had Y here, we had Foo, right? We had nothing. Let's say we had absolutely nothing, right? Foo and then money, right? So what did I do here? We started here at the last character of O, right? And I know that I'm gonna add a character for Y. So I added a character for Y here. And after I added a character for Y, I know that this character Y and Y are the same, right? So I'm, but I don't know if I need to add another character or not, but I do know is that the B character is in the right position, right? The index for B is in the right position. So what that means is I could just move the B pointer down. So for this one, we just move the B pointer down. So when we call our, recursively call our function, we're gonna take the length of B, right? So A is the length of M and B is the length of N. I'm gonna recursively call this function taking N minus one, right? Go to the next character, right? The character before it. So I would call same character, let's say, let's say, recurse, right? Recurse, I'm gonna recurse on the same character of string A and B, but I'm gonna take it and just do N minus one for the current character I'm on to go to the next one, the one before it. And A I'm gonna maintain it on the same position I'm on here. All right, it's still gonna be M, right? Because I might need to add another character. So if like I might need to add another character, like in this case, I might have to add another character E. And in that, in that case, I don't need to move the pointer down again. I that they would be the same, but I have to move B down again, right? I would have to move B down to money, right? So that's, that's for adding. So this is for adding, right? So for adding, we recursively call on M and then N minus one. Okay, now deleting. So let's say I want to delete a character. So let's say I have like way too many characters. So let's say I have like Fui D, okay? Or something like that, okay? One, two, three, one, two, three, four, five D, okay? Well, if I'm deleting character and I'm recursively calling on my function, what does this mean? Okay, so I have my pointer at the set at the last characters, right? D and, D and Y, both of these are the last characters. And I'm going to delete a character on, on A. So what does that mean? That means day A's size, I'm decreasing it by one. So what that means is I'm going to decrease the size by one and I'm going to go to the next character to check for A. But in this case, B's pointer stays the same, right? Because like in this case, I'm deleting a character A, but I still need to check for B's pointer. B's pointer still maintains the same position because like I might need to do something for B's pointer. So for deleting character, so let's, I'm going to actually move this above and move the element below. So like if I take this, whoops, fudge case, okay? So for deleting a character, what we're going to do is we are going to recurse on M minus one, but then it's going to still be the same, same pointer of D, of N. N is going to have the same value, okay? So the last position that we're on, if our current position that we have on our characters for A is M, in our current position we have for B is N, we're going to recurse M minus one and N. Okay, now what if I'm going to replace a character? Well, if you're replacing a character, basically what that means is that, let's say I'm at like, I don't know, let's get rid of all of these characters. Let's say this was, let's say this was a Z, Faws, okay, Faws and Mon. I'm going to replace the character for Z, I'm going to replace it with the character for the last character B. So the Z is going to become N, right? I'm going to replace the Z to become N. Well, in this case, now both of the characters, N and N, are the same. So what we do is we're just going to recurse and just go to the next character of O and O, okay? So that's pretty much all the recursion, recursive calls. So we're replacing, we're just going to recurse on, placing, we're going to recurse on M minus one and N minus one, okay? So yeah, that's pretty much the gist of it. So now when we do our function call, whenever we do this function call, we're just going to do one, because each of these operations take one character and we do plus, we do minimum of recurse on, for adding a character was M and then N minus one. And then for deleting a character, it's recurse on M minus one. And then the final one is just both M minus one and N minus one, okay? So yeah, that's pretty much the gist of it. And this is basically just doing the recursive call. So this recursive call is exponential because we're actually doing like three recursive calls over and over again. So what we currently did was not dynamic programming. What we currently did was just brute forcing it, going through every single possible value to try to check for the right value. So this is exponential. This is like three to the N. And I'm going to show you guys the code now, a picture of the code now, and then I'll explain that now. So this is the picture of the code. Basically, here we have the function called edit distance and it takes string one and string two. Int M is the length of string one, Int N is the length of string two. So if the current index that I'm currently on for M for the first string is empty, so it's equal to zero, I'm going to return N. That means I have to add all the characters from my second string into my first string. So that's why I have to return N. So M and N are like, when we call this through the last character that we're currently on, that means that if we get to an empty, to the get to the zero value, then we have to return all the characters, right? Okay, now the second if statement base case is if N is equal to zero. So this is a case when the string two is empty, right? When string two is empty. If string two is empty, then we have to add all the characters of string one into our first string, right? That's what it is. So what we do is we return M, which is the length of string M, of string one. That's what we have to do, okay? So that's what we have to do, okay? All right, now this is a part with this F statement is if the last characters are the same, then we just recursively call and move to both of the pointers M and N to the next string below, right? Because we start out M and N are the length of the string. So now we just move both of them down by one, right? Because we start, we call this code as M and N as the last character of the string. So now we move by them both down by one. So that's what this edit distance does. Okay, now otherwise we return, this is what we return. Remember, it's only one operation for each of these calls for inserting, removing, and replacing. So what we do is we do return one plus. Now we're gonna call the minimum. The minimum is going to basically do the minimum of recursively calling for edit distance for each of these recursively calls, okay? So the first one we already went over is inserting. So inserting, we're gonna recursively call on M and then N minus one, right? Because our second pointer is in the right location. So yeah, so our character is in the right location. So that's where we have to move the second pointer down by one because our first pointer is in the right location. So we're inserting it, right? Then for removing it, we actually removing a character from M, right? So because we're removing a character from the first string, we're just moving the pointer down by one from the first string. So that's where we remove it. Yeah, that's where we're moving it. And then for replacing, we move both of the pointers down by one because that's what replacing does for edit distance. Okay, so that's basically the gist of the recursive call. So now we're gonna go over the dynamic programming solution. All right guys, so now I'm going to explain the DP solution, which is dynamic programming, which this is pretty much exactly the same thing of what we just did before, except we are going to store the values in a table. So let's look at the solution for this. So in this case, we still have the same strings, right? String one and string two. And string one has size M, string two has size N. So what do we do? We create a DP table that stores the results of each individual index and its minimum sub-problem of the minimum, pretty much the minimum number of operations for each index, for comparing each index to its corresponding value, right? So here we have DP table, which is going to be the size of the string plus one. Okay, the reason why they do plus one is because I think they're trying to index it from one, I think. Oh yeah, they're trying to go through all of it. But yeah, that's why we have M plus one and N plus one. So yeah, okay, so now what do we do? Okay, so we're gonna fill the table bottom up. So what we're gonna do is we're gonna loop through. I is going to loop through from zero to M. What does this mean? I is the index of the current string that we're going through for the string one, right? String one. So for every single index of string one, I is going to represent that going through every single index of string one. J is going to represent the minimum operations for every single index from string two. So from string two, from the first character to second character, third character, fourth character, up to N, that's what J represents. So let's go back to this. I represents the minimum number of operations we needed to have at the current index from I for the first string, right? First string. J represents the minimum number of operations for every single index for all the characters of the second string, that's what I and J represent. Okay, now, if I is equal to zero, what does that mean? That means I is empty. The first string is empty, okay? So what does that mean? If the first string is empty, that means I have to add all the characters. Every single character I have to add into the, from the second string into the first string. So what does that mean? That means if I'm building this DP array, the minimum operations is going to equal to J. J represents the every single index for how many characters I'm adding. So if, let's say I have food and money. J is, if I'm going to add all the characters of J, of all the characters of money from the second string into the first string, that means for each of them at every single index in the first, in the first string and the second string, I have that minimum operations. I'm going to add is the number of characters of the current index I'm currently on, okay? So if it's empty and I want to add the first character, it's going, the minimum operations is going to be one. It's going to be the first character, right? One. Now let's say I'm on the second index, the second character. Well, if I'm an empty string, the minimum number of operations is going to be two, right? Let's say I'm on the third character for the third character. And remember, string one is empty. That means the minimum operations of string one is going to be three, because I need to add three characters into my string one. So that's why our DP of IJ is going to equal to J when I is equal to zero, okay? Same thing coincidentally, if J is equal to zero. If J is equal to zero, that means string two is empty. All the characters of string two is empty. So how many characters do I have to remove from string one? Simple. The minimum operation is going to equal to I. Why? Because let's say I'm at the first character of string one, and I want to remove that. Then if I'm at the first character of string one, how many characters do I have to remove from string one? One. Yeah, I have to remove one character. Now what if I'm at the second character of string one? How many number of characters do I have to remove, assuming I'm removing from the index of two, right? Of the second character of string one? I have to remove two, right? Because I have two indexes of two characters from string one that I want to remove. And if I have three, four, five, six, seven, eight up to the length of M. So that's why we have for the minimum operations, if the second character J is equal to zero, so when the second character is empty, the minimum number of operations to take is going to be basically just to remove every single character out of this current index that you're out for the first string. So that's why DP of IJ is going to equal to I, okay? Now, let's look at the third if statement. This is where basically going to equal to string, checking if the last character that we're currently on, right, for I minus one is equal to J minus one. So if my last character of my two pointers I'm currently on are the same characters, then what do I do? I do absolutely nothing, right? If the two characters are the exact same characters, right? I do absolutely nothing. So what does that mean? That means that my current, if I'm storing every single character at I and J, my current character that I'm going to equal for the minimum number of operations is just going to be the previous characters that I stored. So that's why DP of IJ is going to equal to DP I minus one and J minus one, because I'm not actually increasing the number of operations I need to do. That's why string one, if they're the same characters, I don't have to do anything. I just move on to the next character. So that's why I don't have to increase my counter for the number of operations. So that's why it's going to still equal to the previous what I had previously, DP of I minus one, DP of J minus one. That's what DP of IJ is going to equal to DP I minus one, J minus one. Now, this is where it gets tricky. Otherwise, we have to consider all the other possibilities, right? Else, DP IJ, now it's going to equal to one. Remember, for each of these conditions, each of them, we're doing one operation. Remember, every single condition, inserting, removing, replacing does, it's just one operation because we're moving one character every single time. So that's how you do one. Okay, so DP IJ is going to equal to one. Plus, what do I do? In this case, I'm going to do the minimum. So now, let's look at this. DP, when I insert something, remember, when I insert something, my current index of my current character for my first string, I, is going to remain the same. But my current index of the second character is going to go down by one, right? Current index of the second character goes down by one. Why is this the case? Because I'm inserting a new character into string one. If I'm inserting a new character into string one, I know my second position is going to be in the right position. So I'm going to move that down by one. That's why we do that. So that's why it's DP of IJ minus one for inserting. So for now, for removing, it's the same thing as we're doing the recurrence relation what we had previously. DP is going to, it's going to equal to DP I minus one J. Why is this the case? Because if we go back, DP of I minus one represents removing a character of the current index that I'm currently on for the first string, right? I minus one is for the first string. Remember, I is the index for the first string, J is the index for the second string. So if I'm removing a character from the first string, I'm going to do, that character is going to be in the right position. So that's why I'm going to go to the next. I'm going to go to the next. So I'm going to do I minus one. So string one is going to go to the next character because I know those two pointers are the right values. So I'm going to do I minus one. But my J pointer for the second string at the second character of the second string is we don't know if that's the right position or not, right? If we're removing or not. So that's why we have to remain that at the same position of the same index. So J remains the same thing and I minus one goes down by one. Now what about replacing? Replacing, that means that both characters are now gonna be the same character. They're gonna be the same values. So what does that mean? That means I'm gonna move to the next character downwards. So that's why I'm gonna do I minus one and J minus one. Dp of I minus one, J minus one. So I remove go down for both of the pointers going down. All right. And that's pretty much the gist of it. This Dp arrays builds up all the values of using the recurrence relation while storing every single value for each of the individual sub problems that we solve. So we don't have to recursively call over and over and over again. Dp dynamic programming is literally just finding the recurrence relation, storing the sub problems, then you're done. At the end, we return Dp of Mn. Mn is represents the last values of the string of M and N for string M and N. So that's the exact last value. So that represents basically going back to our recursive solution that represents calling the recursive function, passing in the size of the string one and string two. So Dp Nn is the final value that we have in our Dp table. So that's the last value. Hope you guys enjoyed this video. Rate, com, subscribe. I'll check you guys later. Peace.