Data Structure-Dynamic Programming Algorithm

1. Dynamic programming definition and basic ideas

Dynamic programming is a solution method that decomposes complex problems into simple sub-problems.

The basic idea of ​​dynamic programming: usually used to solve problems with certain optimal properties, trying to solve each problem only once. Once the solution to a given sub-problem has been calculated, it is memorized and stored so that the same sub-problem is needed next time. When solving the problem, look up the table directly. Dynamic programming solves problems from the bottom up (iteratively rather than recursively), avoiding repeated calculations.

For recursion, these subproblems are solved, and then the solutions to the subproblems are combined to obtain the solution to the original problem. The difference is that these sub-problems overlap, and after a sub-problem is solved, it may be solved again.

Therefore, we can store the solutions to these subproblems, and regardless of whether the subproblem is used later, as long as it is calculated, the results will be filled in the table. The next time you use it, just use the results directly. This is the basic idea of ​​dynamic programming method.

Example:

# 动态规划实现斐波那契数列
def fib(n):
    # 递推公式的边界条件
    if n == 0 or n == 1:
        return n

    dp = [0, 1, 0]

    for i in range(1, n):

        sum = dp[0] + dp[1]
        dp[0] = dp[1]
        dp[1] = sum

    return dp[1]
print(fib(10))

# 时间复杂度:O(n)
# 空间复杂度:O(1)

# 递归思路
def fib(n):
    if n == 0 or n == 1:
        return n
    return fib(n-1) + fib(n-2)
print(fib(10))

# 时间复杂度:O(2^n)
# 空间复杂度:O(n)

2. Basic steps of dynamic programming

1. Determine the array that can save the results of the recursive or recursive formula and the meaning of the subscripts. Generally, a one-dimensional array (simpler question) or a two-dimensional array (slightly more difficult) is used to save it. Here it is always represented by dp[]. Among them, we need to understand the one-dimensional dp[i] (array corresponding to the index position). element), two-dimensional array dp[i][j] (in a two-dimensional array, in layman’s terms, it is the element at the corresponding index position of the row and column);

2. dp array initialization. For one-dimensional arrays and two-dimensional arrays, the initial value can help the next time the results appear to be superimposed.

For example: Fibonacci dp[0] = 0, dp[1] = 1, then dp[2] = dp[0] + dp[1].

3. Determine the state transition equation (recursion formula). State transfer is to derive the state of this stage (that is, the connection between different stages) based on the state of the previous stage. In fact, here is whether the results used before are applicable in this stage.

For example: Fibonacci's fib(4) recursively calculates twice, both with the same calculation method fib(4) = fib(3) + fib(2), so this calculation method is a recursive formula. In our dynamic normalization, we save the first calculation result and use the result directly next time.

4. Determine the traversal order. For example: Fibonacci traversal is from front to back. Determining the order of traversal can get results faster.

5. Take the dp array as an example. After completing the above steps, we can give some examples to deduce, or after completing the code, we can use the code editor. I use vscode and pycharm for code debugging.

Here, LeetCode explains these five steps.

Step 1: Determine the array dp, and create an array with m+1 rows and n+1 columns to store the operands of each calculation. Because word1 and word2 exist to perform related operations, we can use a two-dimensional array to convert word1 to word2 and save the minimum number of operations used.

dp = [[0] * (n+1) for i in range(m+1)

Here, you need to understand the meaning of the subscript dp[i][j] : the minimum edit distance between the first i characters of word1 (subscript i-1) and the first j characters of word2 (subscript j-1), for example : word1 = 'op', word2 = 'app', then dp[2][2]=X, which represents the minimum edit distance between 'op' and 'ap', that is, the minimum amount required to convert 'op' to 'ap' operate.

Step 2: Determine the initial value of the array dp. To implement initialization in the table, you need to understand the relationship between dp[i-1][j-1], that is, dp[i][j].

For example: If we want to find the first character dp[i][j] corresponding to word1 and word2 to operate, we need to find the minimum operand of the previous step, which is dp[i-1][j-1]. However, the corresponding element is indeed empty, so when we determine the initial value, we need to consider the situation of dp[i-1][j-1] = 0.

So, now we can determine that the first row element of the initial value is the operand of the empty character that becomes word2 after the addition operation, and the element in the first column is the operand that word1 becomes the empty string after the deletion operation.

Row: word1

Column: word2

 “ ”

null

a ap app appl apple

“ ”

null

0 1 2 3 4 5
o 1
op 2
opp 3
oppa 4
dp[0][0] = 0        # 第一个元素赋值为0
    for i in range(m+1):     # 对word1里的元素全部做删除操作
        dp[i][0] = i
    for j in range(n+1):     # 对word2里的元素进行添加操作
        dp[0][j] = j

The third step is to determine the recursion formula. The most difficult part of this question is the recursion formula. Before understanding the recursion formula, we first determine the possible situations:

1. If word1[i-1] == word2[j-1] during conversion, we do not need to perform the operation. We can directly retain the result of subscript dp[i-1][j-1]. Assigned to this time, that is, dp[i][j] == dp[i-1][j-1].

2. When word1[i-1] != word2[j-1], we need to consider three ways in which operations occur.

That is: word1 replaces word1[i-1]: dp[i][j] = dp[i-1][j-1] + 1    

       word2 deletes an element: dp[i][j] = dp[i][j-1] + 1

       Word1 deletes elements: dp[i][j] = dp[i-1][j] + 1, which is equivalent to word2 adding an element.

dp[i-1][j-1] dp[i-1][j]
dp[i][j-1] dp[i][j]

Why + 1 here?

It is because in these three operations, we have the last result retained in the dp array, and we need to solve for the result of dp[i][j]. Finally, the minimum value of the operands (the least number of operands) is obtained and +1 is obtained.

if word1[i-1] == word2[j-1]:
    dp[i][j] = dp[i-1][j-1]
else:
    dp[i][j] = min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1

The fourth step is to determine the traversal order of the array. Obviously, we need to combine the four recursion formulas, and we can know that the traversal order is from left to right and top to bottom.

Step 5: Verify and deduce through examples

Here we use word1='oppa' and 'apple' for derivation

Row: word1

Column: word2

 “ ”

null

a ap app appl apple

“ ”

null

0 1 2 3 4 5
o 1 1 2 3 4 5
op 2 2 1 2 3 4
opp 3 3 2 1 2 3
oppa 4 3 3 2 2 3

It can be said that most dynamic programming algorithm questions are based on these five steps, so you must pay attention to the meaning of the five steps.

We still need to study more questions to better understand the ideas of dynamic programming.

python code implementation:

def minDistance(word1, word2):

   m = len(word1)
   n = len(word2)
   # 当word1为空的时候,word2的长度就是需要编辑的距离
   if m == 0:
       return n
   
   # 当word2为空的时候,word1的长度就是需要编辑的距离
   if n == 0:
       return m
        
   # 创建一个m+1行n+1列的数组
   dp = [[0]*(n+1) for i in range(m+1)]

   dp[0][0] = 0
   
   # 从 word1[:i] 变成 空串(word2[:0]), 操作即**删除** i 次直到将 word1[:i] 删光到 空串
   for i in range(1,m+1):
       dp[i][0] = i
   
# 从 空串(word1[:0]) 变成 word2[:j], 操作即**插入** j 次直到将 空串 变成 word2[:j]
   for j in range(1,n+1):
       dp[0][j] = j

   for i in range(1, m+1):
       for j in range(1, n+1):
            # 考虑字符是否相等
            if word1[i-1] == word2[j-1]:
               # 如果相等就是上一步操作的步数  
               dp[i][j] = dp[i-1][j-1]
            else:
               # 如果不相等就是附近三个最小值+1
               dp[i][j] = 1+min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) 

    return dp[m][n]

s1 = "oppa"
s2 = "apple"
print(minDistance(s1,s2))

Here, thank you all for browsing, and at the same time, thank you all the outstanding experts for providing information, which made me understand dynamic planning.

If there is an identical article, please inform the author and attach a link. I will attach the link to the corresponding position in this article. Thank you.

Guess you like

Origin blog.csdn.net/m0_71145378/article/details/126837568