CSDN official creation assistant InsCode AI teaches you to get a good article in minutes

CSDN has officially launched the creation assistant InsCode AI for many days. Anyone who cares can find that there is a floating button of the creation assistant in the upper right corner of the writing interface. After clicking, the following interface appears:

At this stage, it is in the state of "restriction and exemption". If you don't make good use of it to create, it will be a bit disappointing to the good intentions of CSDN's official bosses.

Write a blog with the theme "Dynamic Programming Method". The following is the whole process of writing with the help of the creative assistant InsCode AI. It will teach you to get a "good" article in minutes!

Introduction to usage :

1. List several questions with topic clues, such as "what is the dynamic programming method", "introduce the dynamic programming algorithm in detail", "write a dynamic programming algorithm and make a lot of annotations".

2. Insert the answer from InsCode AI into the writing text box, and after sorting out, draw up the multi-level headings for each paragraph.

3. Finally, write a summary of the article, insert the table of contents, add illustrations to beautify it, and save the draft.

Get an article done quickly! The text is as follows:


Table of contents

dynamic programming

definition

condition

step

simple example

topic description

Algorithm implementation

Code 

Advanced example

topic description

1. Bottom-up approach based on recursion

2. Top-down approach based on memoized search

Summary of this article


dynamic programming

definition

The full name is Dynamic Programming, abbreviated as DP. It is a commonly used mathematical algorithm for solving multi-stage decision-making problems. It is usually used to solve problems with some optimal properties, such as maximum, minimum, and so on.

Dynamic programming algorithms are usually based on a recursive formula and one or more initial states. The solution to the current subproblem will be derived from the solution to the previous subproblem. When the dynamic programming algorithm is processing the problem, it will cache the solution of the sub-problem, so that when the same sub-problem is encountered in the future, it can directly look up the table without recalculating. This method can avoid repeated calculations, reduce the amount of calculations, and improve the efficiency of the algorithm.

Dynamic programming algorithms are widely used in solving various optimization problems in economics, management, information, technology, etc.

condition

In general, dynamic programming problems will meet the following conditions:

  1. There are overlapping sub-problems: that is, the sub-problems of a problem are repeated and need to be repeatedly recalculated.

  2. The subproblems have an optimal substructure: that is, the subproblems are independent of each other, and the optimal solution to the subproblem can deduce the optimal solution to the original problem.

  3. No aftereffect: That is, once a certain state is determined, it will not be affected by subsequent decisions.

step

Dynamic programming (Dynamic Programming) is a mathematical method to solve the optimization problem of multi-stage decision-making process. It decomposes the original problem into relatively simple sub-problems, and solves the sub-problems recursively, and finally obtains the optimal solution of the original problem. untie. Dynamic programming algorithms are divided into two types: one is a top-down method based on memory search, and the other is a bottom-up method based on recursion. In both methods, states and state transition equations need to be defined to determine recurrence relations among subproblems. Specifically, we can design a dynamic programming algorithm through the following steps:

  1. Determine the state: find the simplest sub-problem in the problem, and list the state representation. For example, in the maximum sub-sequence problem, the state may be expressed as the maximum sub-sequence sum ending with the i-th number.

  2. Determine the state transition equation: List the state transition equation, that is, how the optimal solution of the current sub-problem is obtained from the optimal solution of the previous sub-problem.

  3. Determine the initial state: Determine the solution to the simplest state among all subproblems, usually a boundary state.

  4. Determine the calculation order: According to the state transition equation, determine the calculation order, usually according to the dimension of the state.

  5. Optimize memory space: If the state transition is only related to the previous state, it is not necessary to cache all states, but only the previous state.

simple example

Taking the LeetCode 509 question "Fibonacci Number" as an example, a dynamic programming algorithm is given with detailed annotations.

topic description

Fibonacci numbers, usually represented by F(n), form a sequence called the Fibonacci sequence. The sequence starts with 0 and 1, and each subsequent number is the sum of the previous two numbers. Right now:

F(0) = 0, F(1) = 1 F(n) = F(n - 1) + F(n - 2), where n > 1.

Example 1:

Input: 2 Output: 1 Explanation: F(2) = F(1) + F(0) = 1 + 0 = 1.

Example 2:

Input: 3 Output: 2 Explanation: F(3) = F(2) + F(1) = 1 + 1 = 2.

Example 3:

Input: 4 Output: 3 Explanation: F(4) = F(3) + F(2) = 2 + 1 = 3.

Algorithm implementation

  1. Determine the state: According to the title description, an array dp[] can be defined to represent the value of the first n numbers in the Fibonacci sequence, and dp[i] represents the value of the i-th number in the Fibonacci sequence.

  2. Determine the state transition equation: According to the definition of the Fibonacci sequence, dp[i] = dp[i-1] + dp[i-2], where i > 1.

  3. Determine the initial state: According to the definition of the Fibonacci sequence, dp[0] = 0, dp[1] = 1.

  4. Determine the calculation order: calculate dp[2], dp[3], ..., dp[n] sequentially from left to right.

  5. Optimize memory space: Since the state transition is only related to the first two states, only two variables can be used to record the values ​​of the first two states, and it is not necessary to cache all states.

Code 

Below is the complete algorithm implementation, with comments explaining each line.

package main

import "fmt"

func fib(n int) int {
	if n < 2 {   // 如果n为0或1,直接返回n
		return n
	}
	dp := [2]int{0, 1}   // 定义初始状态,dp[0]表示F(0),dp[1]表示F(1)
	for i := 2; i <= n; i++ {   // 从2到n按照状态转移方程求解
		dp_i := dp[0] + dp[1]   // 计算dp[i],即F(i)
		dp[0] = dp[1]   // 更新前两个状态
		dp[1] = dp_i
	}
	return dp[1]   // 返回最终结果
}

func main() {
	fmt.Println(fib(2)) // 1
	fmt.Println(fib(3)) // 2
	fmt.Println(fib(4)) // 3
}

The time complexity of this algorithm is O(n) and the space complexity is O(1). The time complexity is linear, and the space complexity is constant level, so it is more practical in practical applications.

Advanced example

Let's take the LeetCode 1143 question "Longest Common Subsequence" (LCS) as an example, using two types of dynamic programming to achieve:

topic description

Given two strings  text1 and  , return the length of the longest common subsequencetext2 of these two strings   .  Returns  if no  common subsequence exists  .0

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  "abcde" a subsequence of , but  "aec" not  "abcde" a subsequence of .

A common subsequence of two strings   is a subsequence that both strings have in common.

Example 1:

Input: text1 = "abcde", text2 = "ace" 
 Output: 3  
 Explanation: The longest common subsequence is "ace" and its length is 3.

Example 2:

Input: text1 = "abc", text2 = "abc"
 Output: 3
 Explanation: The longest common subsequence is "abc", and its length is 3.

Example 3:

Input: text1 = "abc", text2 = "def"
 Output: 0
 Explanation: Two strings have no common subsequence, return 0.

hint:

  • 1 <= text1.length, text2.length <= 1000
  • text1 and  text2 consist of lowercase English characters only.

1. Bottom-up approach based on recursion

In this method, we start from the sub-questions, calculate all the sub-questions in turn, and finally get the answer to the original question. Specifically, we use an array to record the answers to the sub-questions, and then calculate the answers to larger questions based on the results of the sub-questions until the answer to the original question is solved.

For example, when solving the longest common subsequence problem of two strings, we can define a two-dimensional array dp[i][j], which means to calculate the longest common subsequence of the first i characters of string 1 and the first j characters of string 2 sequence. We first initialize all the elements in the array to 0, then traverse all the characters of string 1 and string 2 in turn, and update the elements in the array according to whether the current characters are equal:

  • If the i-th character is the same as the j-th character, add 1 to the length of the longest common subsequence, ie dp[i][j] = dp[i-1][j-1] + 1;
  • If the i-th character and the j-th character are different, the longest common subsequence length is equal to the longest common subsequence length of the longer of the two strings in the previous state, ie dp[i][j] = max(dp[i-1][j], dp[i][j-1]).

Ultimately, dp[m][n]it is the length of the longest common subsequence of string 1 and string 2, where m and n are the lengths of the two strings, respectively. code show as below:

package main

import "fmt"

func MaxLCS(s1, s2 string) int {
	m, n := len(s1), len(s2)
	dp := make([][]int, m+1)
	for i := range dp {
		dp[i] = make([]int, n+1)
	}
	for i := 1; i <= m; i++ {
		for j := 1; j <= n; j++ {
			if s1[i-1] == s2[j-1] {
				dp[i][j] = dp[i-1][j-1] + 1
			} else {
				dp[i][j] = max(dp[i-1][j], dp[i][j-1])
			}
		}
	}
	return dp[m][n]
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func main() {
	text1 := "abcde"
	text2 := "ace"
	fmt.Println(MaxLCS(text1, text2))
	text1 = "abc"
	text2 = "abc"
	fmt.Println(MaxLCS(text1, text2))
	text1 = "abc"
	text2 = "def"
	fmt.Println(MaxLCS(text1, text2))
}

2. Top-down approach based on memoized search

In this approach, we use recursive functions and a memo to implement dynamic programming. The basic idea of ​​a recursive function is to divide the original problem into several sub-problems, solve each sub-problem once, and then cache the results to avoid repeated calculations. The memo records the answers to the sub-problems that have been calculated. If the current problem has been solved before, the result in the memo is returned directly.

First, a function is defined  lcs for recursively finding the longest common subsequence. In this function, we need to pass in two strings s1 and s2, as well as the current traversal index (i and j) and the memory array memo. memo is used to store the length information of strings that have been traversed to avoid repeated calculation of common subsequences, because there are a lot of repeated calculations in the recursive process of calculating common subsequences, and using memo to record existing calculation results can greatly improve calculation efficiency. Before calculation, we first judge whether the two strings currently queried are empty, and if so, return 0 directly. If the current condition already corresponds to the value in the memo array, the corresponding result will be returned directly.

Afterwards, we make judgments by comparing whether the last characters (ie i-1 and j-1) of the currently traversed s1 and s2 strings are equal. If they are equal, put the last character into the common subsequence, increase the length by 1, and search for the next character recursively forward; if they are not equal, remove the last character in s1 or remove the last character in s2 , respectively calculate the common subsequences in the two cases, compare their lengths, and return the largest length.

In  MaxLCS the function, we define a memo array to store the length information of the strings that have been traversed. When cyclically initializing the memo array, we set the value of each position to -1, indicating that no calculation has been performed. Finally, we pass the two tail subscripts of s1 and s2 (ie len(s1) and len(s2)) and the memo array into the function  lcs to get the final length of the longest common subsequence.

code show as below:

package main

import "fmt"

func lcs(s1, s2 string, i, j int, memo [][]int) int {
	if i == 0 || j == 0 {
		return 0
	}
	if memo[i][j] != -1 {
		return memo[i][j]
	}
	if s1[i-1] == s2[j-1] {
		memo[i][j] = lcs(s1, s2, i-1, j-1, memo) + 1
	} else {
		memo[i][j] = max(lcs(s1, s2, i-1, j, memo), lcs(s1, s2, i, j-1, memo))
	}
	return memo[i][j]
}

func MaxLCS(s1, s2 string) int {
	memo := make([][]int, len(s1)+1)
	for i := range memo {
		memo[i] = make([]int, len(s2)+1)
		for j := range memo[i] {
			memo[i][j] = -1
		}
	}
	return lcs(s1, s2, len(s1), len(s2), memo)
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func main() {
	text1 := "abcde"
	text2 := "ace"
	fmt.Println(MaxLCS(text1, text2))
	text1 = "abc"
	text2 = "abc"
	fmt.Println(MaxLCS(text1, text2))
	text1 = "abc"
	text2 = "def"
	fmt.Println(MaxLCS(text1, text2))
}

Summary of this article

Dynamic programming is an algorithm for solving complex problems by splitting the original problem into sub-problems. In dynamic programming, by recording the results of previous calculations, repeated calculations can be avoided, thereby improving the efficiency of the algorithm.

In dynamic programming problems, it is usually necessary to define states, determine state transition equations and boundary conditions. The problem can be decomposed into smaller sub-problems through the state transition equation, and the solution results of the sub-problems are stored in a table to facilitate subsequent calculations.

Dynamic programming is often used to solve problems such as longest common subsequence, longest increasing subsequence, knapsack problem, and string edit distance. When solving these problems, we need to design the state and state transition equations that conform to the actual situation by analyzing the special properties of the problem.

It should be noted that when designing state transition equations, attention needs to be paid to the calculation order, especially when the calculation of a certain state depends on multiple previous states, the calculation order needs to be carefully arranged to avoid erroneous results.

In short, dynamic programming is an effective algorithmic idea that can solve many practical problems. Although certain thinking difficulty and skills are required, as long as the basic principles and methods are mastered, they can be flexibly applied to various scenarios and solve various problems.


After finishing, don’t rush to publish, move the mouse to the bottom button “Save Draft”, click it after “Save and Preview” appears, get the article link, copy and paste it to the CSDN quality score query page to check the score:

https://www.csdn.net/qc

Yes, it really is a "good" article! It can be published. If you feel that this article is helpful, please like it and write a comment. Thanks!

Guess you like

Origin blog.csdn.net/boysoft2002/article/details/130776791