Algorithm Routine 15 - Dynamic Programming to Solve the Longest Common Subsequence LCS

Algorithm Routine 15 - Dynamic Programming to Solve the Longest Common Subsequence LCS

Algorithm Example: LeetCode1143. Longest Common Subsequence

Given two strings text1 and text2, return the length of the longest common subsequence of these two strings. Returns 0 if no common subsequence exists.
A subsequence of a string refers to such a new string: it is a new string formed by deleting some characters (or not deleting any characters) from the original string without changing the relative order of the characters.
For example, "ace" is a subsequence of "abcde", but "aec" is not a subsequence of "abcde".
A common subsequence of two strings is a subsequence that both strings have in common.
insert image description here

Two-dimensional array dynamic programming

  1. define dp [ ] [ ] dp[][]dp[][]:设 d p [ i ] [ j ] dp[i][j] d p ​​[ i ] [ j ] represents the sequenceXXex iiof xi characters and sequenceYYY 's exjjThe longest common subsequence length of j characters.

  2. Initialization: when i = 0 i=0i=0 orj = 0 j=0j=0 时, d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0 , because an empty sequence has no common part with any subsequence of another sequence.

  3. State transition equation: For sequence XXiiin Xi characters and sequenceYYthe jjthin Yj characters, there are two cases:

    • Results X i = Y j X_i = Y_jXi=Yj, then the current character can be included in the longest common subsequence, so dp [ i ] [ j ] = dp [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1 ][j-1] + 1dp[i][j]=dp[i1][j1]+1
    • Result X i ≠ Y j X_i \neq Y_jXi=Yj, then the current character cannot appear in the longest common subsequence at the same time, so take the larger value of the two cases: remove X i X_iXifollowed by the sequence YYY 's exjjlongest common subsequence length of j characters, or with the sequence XXex iiof xi characters removeY j Y_jYjThe length of the longest common subsequence after . That is, dp [ i ] [ j ] = max ⁡ ( dp [ i − 1 ] [ j ] , dp [ i ] [ j − 1 ] ) dp[i][j] = \max(dp[i-1][ j], dp[i][j-1])dp[i][j]=max(dp[i1][j],dp[i][j1])
  4. The state transition equation and dp array initialization are as follows:
    dp [ i + 1 ] [ j + 1 ] = { 0 , i = 0 , j > 0 0 , i > 0 , j = 0 dp [ i ] [ j ] + 1 , i , j > 0 , X i = Y j max ⁡ ( dp [ i + 1 ] [ j ] , dp [ i ] [ j + 1 ] ) , i , j > 0 , X i ≠ Y i dp[ i+1][j+1] = \begin{cases} 0, & i=0, j>0 \\ 0, & i>0, j=0 \\ dp[i][j]+1, & i,j>0,X_i=Y_j \\ \max(dp[i+1][j], dp[i][j+1]), & i,j>0,X_i \neq Y_i \end{cases }dp[i+1][j+1]= 0,0,dp[i][j]+1,max(dp[i+1][j],dp[i][j+1]),i=0,j>0i>0,j=0i,j>0,Xi=Yji,j>0,Xi=Yi

  5. Return Value
    Ultimately, dp [ m ] [ n ] dp[m][n]d p ​​[ m ] [ n ] is the sequenceXXX and sequenceYYThe longest common subsequence length of Y , where mmm andnnn is sequence XXrespectivelyXYYThe length of Y.

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n, m = len(text1), len(text2)
        dp = [[0]*(m+1) for _ in range(n + 1)]
        for i in range(n):
            for j in range(m):
                if text1[i]==text2[j]:
                    dp[i+1][j+1]=dp[i][j]+1
                else:
                    dp[i+1][j+1]=max(dp[i][j + 1], dp[i + 1][j])
        return dp[n][m]

Space-optimized as a 1D array

The state transition array of this question needs to consider dp [ i ] [ j ], dp [ i ] [ j + 1 ], dp [ i + 1 ] [ j ] dp[i][j], dp[i][j + 1 ], dp[i + 1][j]dp[i][j]dp[i][j+1]dp[i+1 ] [ j ] The three states are respectively located on the left side, the upper side and the upper left side of the two-dimensional array. For the upper left state, dp[i][j], in the two-dimensional matrix, update the i+1th row j+ The elements in column 1 need to use the elements in column j of row i and column j+1 of row i. In a one-dimensional array, if the value of dp[j+1] is directly overwritten, the element in the i-th row and column j will be covered by the new value, so that it cannot be calculated when other elements in the i+1-th row continue to use. Therefore, we need to record the old value and store it in the temp variable while updating dp[j+1], so that it can be used in the next iteration.

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n, m = len(text1), len(text2)
        dp = [0]*(m+1)
        for i in range(n):
            temp = dp[0]
            for j in range(m):
                if text1[i]==text2[j]:
                    # dp[j+1] 表示左上角的值,dp[j+1] = temp + 1 表示将左上角的值加一,赋值给当前单元格;
                    # 同时将更新前的 dp[j+1] 赋值给 temp,以便下次迭代中计算下一个单元格所需的左上角的值。
                    dp[j+1], temp = temp+1, dp[j+1]
                else:
                    dp[j+1], temp = max(dp[j], dp[j+1]), dp[j+1]
        return dp[m]

Algorithm Exercise 1: LeetCode583. Deletion operation of two strings

Given two words word1 and word2, return the minimum number of steps required to make word1 and word2 the same.
Each step can delete one character in any string.
insert image description here

As shown in the figure above, the key to this question is to find the longest common subsequence LCS of the two strings. After finding it, the answer can be directly obtained as len(word1)+len(word2)-2*len(lcs), so we directly use The example function longestCommonSubsequence() finds len(lcs)

func minDistance(word1 string, word2 string) int {
    
    
    lcsLen := one_dimensional_longestCommonSubsequence(word1, word2)
    return len(word1) + len(word2) - 2*lcsLen
}
func one_dimensional_longestCommonSubsequence(text1 string, text2 string) int {
    
    
    n, m := len(text1), len(text2)
    dp := make([]int, m+1)
    for i := 0; i < n; i++ {
    
    
        temp := dp[0]
        for j := 0; j < m; j++ {
    
    
            if text1[i] == text2[j] {
    
    
                // dp[j+1] 表示左上角的值,dp[j+1] = temp + 1 表示将左上角的值加一,
                // 赋值给当前单元格;同时将更新前的 dp[j+1] 赋值给 temp,
                // 以便下次迭代中计算下一个单元格所需的左上角的值。
                dp[j+1], temp = temp+1, dp[j+1]
            } else {
    
    
                dp[j+1], temp = max(dp[j], dp[j+1]), dp[j+1]
            }
        }
    }
    return dp[m]
}
func max(a, b int) int {
    
        if a > b {
    
    return a};    return b}

Algorithm Exercise 2: LeetCode712. Minimal ASCII Deleted Sum of Two Strings

Given two strings s1 and s2, return the minimum sum of the ASCII values ​​of the characters removed to make the two strings equal.
> ![Insert picture description here](https://img-blog.csdnimg.cn/50c76a939ff8486dae3f733ccb3ab02a.png)

This question is similar to the previous question, the only difference is that what is sought is the maximum AscII value of the common subsequence, so we can modify the state transition equation as follows:
dp [ i + 1 ] [ j + 1 ] = { 0 , i = 0 , j > 0 0 , i > 0 , j = 0 dp [ i ] [ j ] + ASCII value of X i, i , j > 0 , X i = Y j max ⁡ ( dp [ i + 1 ] [ j ] , dp [ i ] [ j + 1 ] ) , i , j > 0 , X i ≠ Y i dp[i+1][j+1] = \begin{cases} 0, & i= 0, j>0 \\ 0, & i>0, j=0 \\ dp[i][j]+ASCII value of X_i, & i,j>0,X_i=Y_j \\ \max(dp[i +1][j], dp[i][j+1]), & i,j>0,X_i \neq Y_i \end{cases}dp[i+1][j+1]= 0,0,dp[i][j]+XiA SC II value , _max(dp[i+1][j],dp[i][j+1]),i=0,j>0i>0,j=0i,j>0,Xi=Yji,j>0,Xi=Yi

func minimumDeleteSum(s1 string, s2 string) int {
    
    
    lcaAsCII:=one_dimensional_largestAsCII_common_subsequence(s1,s2)
    return sumAsCII(s1)+sumAsCII(s2)-2*lcaAsCII
}
func  one_dimensional_largestAsCII_common_subsequence(text1 string, text2 string) int {
    
    
    n, m := len(text1), len(text2)
    dp := make([]int, m+1)
    for i := 0; i < n; i++ {
    
    
        temp := dp[0]
        for j := 0; j < m; j++ {
    
    
            if text1[i] == text2[j] {
    
    
				#唯一区别temp+1修改为temp+int(text1[i])
                dp[j+1], temp = temp+int(text1[i]), dp[j+1]
            } else {
    
    
                dp[j+1], temp = max(dp[j], dp[j+1]), dp[j+1]
            }
        }
    }
    return dp[m]
}
func max(a, b int) int {
    
        if a > b {
    
    return a};    return b}
func sumAsCII(str string)(sum int){
    
    
    for _,c:=range str{
    
    
        sum+=int(c)
    }
    return sum
}

Advanced Algorithm 1: LeetCode1458. Maximum dot product of two subsequences

You are given two arrays nums1 and nums2.
Please return the maximum dot product of two non-empty subsequences of the same length in nums1 and nums2.
The non-empty subsequence of the array is the sequence composed of the remaining numbers after deleting some elements in the original array (maybe none of them are deleted), but the relative order of the numbers cannot be changed. For example, [2,3,5] is a subsequence of [1,2,3,4,5] and [1,5,3] is not.
insert image description here

Two-dimensional array dynamic programming

In the LCS problem, when text1[i] and text2[j] are equal, the final result can be +1 or ASCII value by adding this same element to the longest common subsequence. But in the maximum dot product problem, 若nums1[i]和nums2[j]的积大于0,表明它们的符号相同,这只是可能会对最终结果产生正影响,但并不能确定它们一定需要配对,通过比较不同需求下的dp数组之间的大小关系,才能得出最大的点积值, as follows:

For example, nums1 = [5, -2, 3], nums2 = [-4, -1, 2] can get dp [ 1 ] [ 1 ] = 0 and num 1 [ 1 ] ∗ nums 2 [ 1 ] = ( − 2 ) ∗ ( − 1 ) = 2 , but at this time dp [ 1 ] [ 2 ] = 0 , dp [ 2 ] [ 1 ] = ( − 2 ) ∗ ( − 4 ) = 8 can get dp[1][ 1]=0 and num1[1]*nums2[1]=(-2)*(-1)=2, but at this time there are still dp[1][2]=0, dp[2][1]= (-2)*(-4)=8can get d p [ 1 ] [ 1 ]=0 and n u m 1 [ 1 ]nums2[1]=(2)(1)=2 , but there is still d p [ 1 ] [ 2 ]=0dp[2][1]=(2)(4)=8In this case, although nums1[i]*nums2[j] is greater than 0, we still need to consider the value of dp[2][1], so at this time dp [ 2 ] [ 2 ]= max ( dp [ 2 ] [ 1 ] , dp [ 1 ] [ 2 ] , dp [ 1 ] [ 1 ] + num 1 [ 1 ] ∗ nums 2 [ 1 ] ] = 8 dp[2][2]=max(dp[2][1 ],dp[1][2],dp[1][1]+num1[1]*nums2[1]]=8dp[2][2]=max(dp[2][1],dp[1][2],dp[1][1]+n u m 1 [ 1 ]nums2[1]]=8

First define a two-dimensional array dp, where dp[i][j] represents the maximum dot product of two subsequences composed of the first i numbers of nums1 and the first j numbers of nums2.
The state transition equation is as follows: dp [ i ] [ j ] = max ( dp [ i − 1 ] [ j − 1 ] + nums 1 [ i − 1 ] ∗ nums 2 [ j − 1 ] , dp [ i ] [ j − 1 ] , dp [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i-1][j-1] + nums1[i-1]*nums2[j-1], dp [i][j-1], dp[i-1][j])dp[i][j]=max(dp[i1][j1]+nums1[i1]nums2[j1]dp[i][j1]dp[i1 ] [ j ])
dp[i-1][j-1] +nums1[i-1]*nums2[j-1] indicates that the current last element is taken out from the two arrays for dot product; dp[i ][j-1] and dp[i-1][j] respectively indicate that nums1[i-1] and nums2[j-1] are not taken, and only the maximum dot product obtained by selecting the previous elements is selected.

func maxDotProduct(nums1 []int, nums2 []int) int {
    
    
    return two_dimensional_Max_dot_product(nums1,nums2)
}
func two_dimensional_Max_dot_product(nums1 []int, nums2 []int) int {
    
    
    n, m := len(nums1), len(nums2)
    dp := make([][]int, n+1)
    for i := range dp {
    
    
        dp[i] = make([]int, m+1)
    }
    ans:=math.MinInt
    for i := 1; i <= n; i++ {
    
    
        for j := 1; j <= m; j++ {
    
    
            acc:=nums1[i-1]* nums2[j-1]
            dp[i][j] = maxOfThree(dp[i-1][j-1]+acc , dp[i-1][j], dp[i][j-1])
            ans=max(ans,acc)
        }
    }
    if dp[n][m]==0{
    
    return ans}
    return dp[n][m]
}
func maxOfThree(a, b, c int) int {
    
    
    max := a
    if b > max {
    
    max = b}
    if c > max {
    
    max = c}
    return max
}
func max(a, b int) int {
    
        if a > b {
    
    return a};    return b}

Space-optimized as a 1D array

Space optimization is the same as the LCS problem, you need to use temp records, there is no difference between the state transition equation and the initial array

func maxDotProduct(nums1 []int, nums2 []int) int {
    
    
    return one_dimensional_Max_dot_product(nums1,nums2)
}
func one_dimensional_Max_dot_product(nums1 []int, nums2 []int) int {
    
    
    n, m := len(nums1), len(nums2)
    dp := make([]int, m+1)
    ans:=math.MinInt
    for i := 0; i < n; i++ {
    
    
        temp := dp[0]
        for j := 0; j < m; j++ {
    
    
            acc:=nums1[i]*nums2[j]
            dp[j+1], temp =maxOfThree(temp+acc,dp[j],dp[j+1]),dp[j+1]
            ans=max(ans,acc)
        }
    }
    //如果dp[m]==0,说明ans即最大的乘积acc<=0,故可直接返回acc
    if dp[m]==0{
    
    return ans}
    return dp[m]
}
func maxOfThree(a, b, c int) int {
    
    
    max := a
    if b > max {
    
    max = b}
    if c > max {
    
    max = c}
    return max
}
func max(a, b int) int {
    
        if a > b {
    
    return a};    return b}

Advanced Algorithm 2: LeetCode97. Interleaved Strings

Given three strings s1, s2, s3, please help to verify whether s3 is composed of s1 and s2 interleaved.
The definition and process of interleaving two strings s and t are as follows, where each string will be divided into several non-empty substrings:
s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
interleaving is s1 + t1 + s2 + t2 + s3 + t3 + ... or t1 + s1 + t2 + s2 + t3 + s3 + ...
Note: a + b means string a and b concatenated.
insert image description here

What is wrong with the double-pointer method? Perhaps the first reaction of some students when they see this question is to use the double-pointer method to solve this problem. The pointer p1 points to the head of s1 at the beginning, the pointer p2 points to the head of s2 at the beginning, and the pointer
p3 Point to the head of s3, each time you observe which element pointed to by p1 and p2 is equal to the element pointed to by p3, if they are equal, match and move the pointer back.
If it is judged that s1 = bcc, s2 = bbca, and s3 = bbcbcac, when p3 points to b of s3, both s1 and s2 have b, so it is still necessary to record the p1 pointer advance and p2 pointer advance respectively at this time. Determine whether each case can meet the string interleaving, so it cannot be solved by using a single double-pointer method.

Recursive + memoized search

This problem is not a general LCS problem, and it is not easy to understand the state transition equation, so we first use the method of recursion + memory search to solve this problem.
We can define a recursive function dfs(i, j, k), where s1, s2 and s3 represent three strings respectively, and i, j and k represent the current matching positions of s1, s2 and s3 respectively.

The basic idea of ​​a recursive function is as follows:

  1. Return True if i, j and k are all equal to the length of the string, indicating that s3 can be composed of s1 and s2 interleaved.

  2. If k is equal to the length of the string s3, but i or j is not equal to the length of the corresponding string, it means that s3 cannot be composed of s1 and s2 interleaved, return False.

  3. If s1[i] is equal to s3[k], then we can recursively judge whether s1[i+1:], s2[j:] and s3[k+1:] can be interleaved. Return True if it can.

  4. If s2[j] is equal to s3[k], then we can recursively judge whether s1[i:], s2[j+1:] and s3[k+1:] can be interleaved. Return True if it can.

  5. If s1[i] and s2[j] are not equal to s3[k], then s3 cannot be composed of s1 and s2 interleaved, return False.

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        @cache
        def dfs(i, j, k):
            if i == len(s1) and j == len(s2) and k == len(s3):
                return True
            if k == len(s3) and (i != len(s1) or j != len(s2)):
                return False
            if i < len(s1) and s1[i] == s3[k] and dfs(i+1, j, k+1):
                return True
            if j < len(s2) and s2[j] == s3[k] and dfs(i, j+1, k+1):
                return True
            return False
        return dfs(0, 0, 0)

Two-dimensional array dynamic programming

According to the above recursive process, we can get the formula of dynamic programming,

  1. dp array: dp[i][j] indicates whether the first i characters of s1 and the first j characters of s2 can be interleaved to form the first i+j characters of s3.
  2. Initialization: dp[0][0] = True, because empty strings can form empty strings.
  3. Dynamic transfer equation:

d p [ i ] [ j ] = { t r u e , i = 0 , j = 0 d p [ 0 ] [ j − 1 ]  and  s 2 [ j − 1 ] = s 3 [ j − 1 ] , i = 0 , j ≥ 1 d p [ i − 1 ] [ 0 ]  and  s 1 [ i − 1 ] = s 3 [ i − 1 ] , i ≥ 1 , j = 0 ( d p [ i − 1 ] [ j ]  and  s 1 [ i − 1 ] = s 3 [ i + j − 1 ] )  or  ( d p [ i ] [ j − 1 ]  and  s 2 [ j − 1 ] = s 3 [ i + j − 1 ] ) , i ≥ 1 , j ≥ 1 dp[i][j] = \begin{cases} true, & i=0, j=0 \\ dp[0][j-1] \text{ and } s2[j-1] = s3[j-1], & i=0, j \geq 1 \\ dp[i-1][0] \text{ and } s1[i-1] = s3[i-1], & i \geq 1, j=0 \\ (dp[i-1][j] \text{ and } s1[i-1] = s3[i+j-1]) \text{ or } (dp[i][j-1] \text{ and } s2[j-1] = s3[i+j-1]), & i \geq 1, j \geq 1 \end{cases} dp[i][j]= true,dp[0][j1] and s2[j1]=s 3 [ j1],dp[i1][0] and s1[i1]=s 3 [ i1],(dp[i1][j] and s1[i1]=s 3 [ i+j1]) or (dp[i][j1] and s2[j1]=s 3 [ i+j1]),i=0,j=0i=0,j1i1,j=0i1,j1

  1. Return value: The final answer is dp[n][m], where n and m are the lengths of s1 and s2 respectively.
func isInterleave(s1 string, s2 string, s3 string) bool {
    
    
    n,m:=len(s1),len(s2)
    if n+ m != len(s3) {
    
    
        return false
    }
    dp := make([][]bool, n+ 1)
    for i := range dp {
    
    
        dp[i] = make([]bool, m + 1)
    }
    dp[0][0] = true
    for i := 1; i <= n; i++ {
    
    
        dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1]
    }
    for j := 1; j <= m; j++ {
    
    
        dp[0][j] = dp[0][j-1] && s2[j-1] == s3[j-1]
    }
    for i := 1; i <= n; i++ {
    
    
        for j := 1; j <= m; j++ {
    
    
            /*if s1[i-1] == s3[i+j-1]{
                dp[i][j] =dp[i][j] || dp[i-1][j]
            }
            if  s2[j-1] == s3[i+j-1]{
                dp[i][j] =dp[i][j] || dp[i][j-1]
            }*/
            dp[i][j] = (dp[i-1][j] && s1[i-1] == s3[i+j-1]) || (dp[i][j-1] && s2[j-1] == s3[i+j-1])
        }
    }
    return dp[n][m]
}

space optimization

func isInterleave(s1 string, s2 string, s3 string) bool {
    
    
    n1, n2, n3 := len(s1), len(s2), len(s3)
    if n1 + n2 != n3 {
    
    
        return false
    }

    dp := make([]bool, n2 + 1)
    dp[0] = true

    for i := 0; i <= n1; i++ {
    
    
        for j := 0; j <= n2; j++ {
    
    
            if i == 0 && j > 0 {
    
    
                dp[j] = dp[j-1] && (s2[j-1] == s3[i+j-1])
            } else if i > 0 && j == 0 {
    
    
                dp[j] = dp[j] && (s1[i-1] == s3[i+j-1])
            } else if i > 0 && j > 0 {
    
    
                dp[j] = (dp[j] && (s1[i-1] == s3[i+j-1])) ||
                        (dp[j-1] && (s2[j-1] == s3[i+j-1]))
            }
        }
    }

    return dp[n2]
}

Advanced algorithm three: LeetCode72. Edit distance

Given you two words word1 and word2, please return the minimum number of operations used to convert word1 into word2.
You can perform the following three operations on a word:
Insert a character
Delete a character
Replace a character
insert image description here

Recursive + memoized search

  1. Recursive function definition: The recursive function dfs accepts two parameters i and j, respectively representing the index of word1 and word2. The return value of the dfs function is the minimum number of operations required to transform word1 into word2. In this function, we use the functools.cache decorator in Python 3 to cache the result of the recursive function to avoid double computation.
  2. Recursive equation:
    If i < 0, it means that word1 has been traversed, and j+1 characters need to be inserted to match word2, so j+1 is returned.
    If j < 0, it means that word2 has been traversed, and i+1 characters need to be inserted to match word1, so i+1 is returned.
    If word1[i] == word2[j], it means that the current characters are equal, no operation is required, and recursively goes to the next character, namely dfs(i-1, j-1).
    If word1[i] != word2[j], it means that the current characters are not equal and need to be inserted, deleted or replaced. Specifically:
    • Insertion operation: insert a character into word1, so that word1[i+1] == word2[j], and then recurse to dfs(i, j-1).
    • Delete operation: delete a character in word1, so that word1[i-1] == word2[j], then recurse to dfs(i-1, j).
    • Replacement operation: replace a character in word1 with a character in word2, so that word1[i-1] == word2[j-1], then recurse to dfs(i-1, j-1).
    • Among these three operations, take the one with the least number of operations, namely min(dfs(i-1, j), dfs(i, j-1), dfs(i-1, j-1)), and then add 1, you can get the minimum number of operations required to convert word1 into word2.
  3. Boundary condition: In the recursive function, we need to judge whether word1 and word2 have been traversed, and if so, return the length of another string plus 1, because at this time, characters need to be inserted to match another string. Specifically, if i < 0, return j + 1; if j < 0, return i + 1.
  4. Evaluated value: We need to call the recursive function, pass in the length of word1 and word2 minus 1 as a parameter, and return the final edit distance. Specifically, we need to return dfs(n - 1, m - 1), where n and m are the lengths of word1 and word2 respectively.

The following is a case-by-case explanation of the recursive process:

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n,m=len(word1),len(word2)
        @cache
        def dfs(i:int,j:int)->int:
            if i<0:
                return j+1
            if j<0:
                return i+1
            if word1[i]==word2[j]:
                return dfs(i-1,j-1)
            else:
                return min(dfs(i-1,j),dfs(i,j-1),dfs(i-1,j-1))+1
        return dfs(n-1,m-1)

Two-dimensional array dynamic programming

According to the above recursive process, we can get the formula of dynamic programming.
The following is the dynamic programming solution of the edit distance problem:

  1. Definition state: dp[i][j] represents the minimum number of operations required to convert the first i characters of word1 into the first j characters of word2.

  2. Initialization: When the length of the string is 0, no operation is required for mutual conversion. So dp[0][j]=j, dp[i][0]=i.

  3. Transfer equation:

    • When word1[i] == word2[j], no operation is required at this time, so dp[i][j] = dp[i-1][j-1].
    • When word1[i] != word2[j], it is necessary to perform one of the three operations of insertion, deletion, and replacement. Take the one with the least number of operations among these three operations, and add 1 to get dp [i][j]:
      • Insert operation: dp[i][j] = dp[i][j-1] + 1
      • Delete operation: dp[i][j] = dp[i-1][j] + 1
      • 替换操作: dp[i][j] = dp[i-1][j-1] + 1
        d p [ i ] [ j ] = { 0 , i = 0 , j = 0 i , i ≥ 1 , j = 0 j , i = 0 , j ≥ 1 min ⁡ ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j − 1 ] ) + 1 ,   w o r d 1 [ i − 1 ] ≠ w o r d 2 [ j − 1 ] d p [ i − 1 ] [ j − 1 ] w o r d 1 [ i − 1 ] = w o r d 2 [ j − 1 ]   dp[i][j] = \begin{cases} 0, & i=0, j=0 \\ i, & i \geq 1, j=0 \\ j, & i=0, j \geq 1 \\ \begin{aligned} & \min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1, \ & \quad \text word1[i-1] \neq word2[j-1]\\ & \quad dp[i-1][j-1]& \quad \text word1[i-1] = word2[j-1]\ \end{aligned} \end{cases} dp[i][j]= 0,i,j,min(dp[i1][j],dp[i][j1],dp[i1][j1])+1, dp[i1][j1]word1[i1]=word2[j1]word1[i1]=word2[j1] i=0,j=0i1,j=0i=0,j1
  4. Boundary case: when i = 0, it means that word1 has been traversed, and j characters need to be inserted to match word2; similarly, when j = 0, i characters need to be deleted to match word2.

  5. The desired answer: the final answer is stored in dp[n][m], where n and m are the lengths of word1 and word2 respectively.

func minDistance(word1 string, word2 string) int {
    
    
    n,m:=len(word1),len(word2)
    dp:=make([][]int,n+1)
    for i:=0;i<=n;i++{
    
    
        dp[i]=make([]int ,m+1)
    }
    for i:=0;i<=n;i++{
    
    
        dp[i][0]=i
    }
    for j:=0;j<=m;j++{
    
    
        dp[0][j]=j
    }
    for i:=0;i<n;i++{
    
    
        for j:=0;j<m;j++{
    
    
            if word1[i]==word2[j]{
    
    
                dp[i+1][j+1]=dp[i][j]
            }else{
    
    
                dp[i+1][j+1]=min(dp[i][j+1],dp[i+1][j],dp[i][j])+1
            }
        }
    }
    return dp[n][m]
}
func min(x, y, z int) int {
    
    
   if x < y && x < z {
    
    
      return x
   } else if y < x && y < z {
    
    
      return y
   }
   return z
}

Guess you like

Origin blog.csdn.net/qq_45808700/article/details/130406425