Top-down Solution

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        m,n = len(word1), len(word2)
        # edge case:
        if not word1 or not word2:
            return m + n
        # create memo 
        cache = [[-1]*(n) for _ in range(m)]
        return self.match(word1,word2,0,0,cache)
        #return memo[-1][-1]
    
    def match(self,w1,w2,i,j,memo):
        # termination conditions: 3 cases 
        if i == len(w1) and j == len(w2):
            return 0
        if i == len(w1):
            return len(w2)-j
        if j == len(w2):
            return len(w1) -i
        # check memo
        if memo[i][j]==-1: # i,j not saved in memo
            # match: return memo[i][j]
            if w1[i] == w2[j]:
                res = self.match(w1,w2,i+1,j+1,memo)
            # not match 
            else:
                # 3 cases: 1. insert 2. delete 3. replace ; each operation +1 cost  
                insert = 1 + self.match(w1,w2,i,j+1,memo)
                delete = 1 + self.match(w1,w2,i+1,j,memo)
                replace = 1 + self.match(w1,w2,i+1,j+1,memo)
                # find min cost 
                res = min(insert, delete, replace)
            memo[i][j] = res
        
        return memo[i][j] 
        
            

Bottom up solution

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # time O(len1*len2)  space O(len1*len2)
        # 3 loops 
        m = len(word1)
        n = len(word2)
        table = [[0]*(n+1) for _ in range(m+1)]
        
        for i in range(1,m+1):
            table[i][0] = i
        for j in range(1,n+1):
            table[0][j] = j 
        
        for i in range(1,m+1):
            for j in range(1,n+1):
                if word1[i-1] == word2[j-1]:
                    table[i][j] = table[i-1][j-1]
                else:
                    table[i][j] = 1 + min(table[i-1][j],table[i][j-1],table[i-1][j-1])
                
        return table[-1][-1]