Preface
In the previous article dynamic programming , we first introduced the Fibonacci example into dynamic programming, and then analyzed the three main properties of dynamic programming with the help of the example of exchange of change, namely:
- Overlapping subproblems
- Optimal substructure
- State transition equation
But dynamic programming is much more than that.
Today's article, let us in-depth dynamic programming, a glimpse of the essence of dynamic programming.
Since we want to thoroughly understand dynamic programming, then an inevitable problem is:
What is the difference between recursion, greedy, memory search and dynamic programming?
- dynamic programming in recursive : Is it just pure space for time? No, the Fibonacci sequence disproves this view very well.
- Dynamic Programming to Greedy : Is it just an enhanced version of Greedy? change to 16076b6213b91c also overturns this view.
So, what is the core of dynamic programming
To answer this question, we might as well answer the following question first:
What problems are suitable for dynamic programming? How to identify DP solvable problems?
I believe that when we recognize which problems can be solved with DP, we naturally find the difference between DP and other algorithm ideas, which is the core of dynamic programming
Dynamic programming core
First, we must be clear, dynamic programming applies only to a certain class of problems , just some sort of problem solutions.
So what is the problem of this "certain type of problem"?
Before we talk about this, we need to understand a little bit about the nature of computers.
A computer based on the von Neumann architecture is essentially a state machine . Why do you say that? Because the CPU must deal with memory in order to perform calculations.
Because the data is stored in the memory (the nature of the register and the external disk is the same), the CPU does not have the data to calculate the air? Therefore, the memory is used to store the state (data), all the data currently stored in the memory constitutes the current state , the CPU can only use the current state calculate the next state .
When we use computers to deal with problems, we are just thinking: how to use variables to store the state , and how to state : calculate some variables from some variables, and calculate the next state from the current state.
Based on these, we also get the two main indicators of judging algorithm
- Space complexity : It is to support the state of storage necessary for calculation
- Time complexity : How many steps are required from the initial state to the final state
If the above statement is not very clear, then let's take the example of Fibonacci before:
- To calculate the current f(n), you only need to know f(n-1) and f(n-2).
which is:
- To calculate the current state f(n), you only need to calculate the states f(n-1) and f(n -2).
That is to say, the current state is only related to the first two states, so for the space complexity: we only need to save the first two states.
This also explains why dynamic programming is not simply a space for time, because it is actually only related to state.
The calculation time required to transfer from one state state is also constant, so the linearly increasing state has a linear total time complexity.
The above is the core of dynamic programming, namely:
The definition of state and the transition between states (the definition of state equation).
So how to define the so-called " state " and " state transition "?
We introduce the definition of Wikipedia:
dynamic programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems.
It is through splitting problem , define the problem state and state relationship between such problems can be recursive (or partition) way to solve.
After talking on the paper, I finally feel shallow, let's look at another example that is also very classic.
Longest increasing subsequence
This is LeetCode question 300.
Given a sequence of numbers with length N, find the length of the longest rising (increasing) sub-sequence (LIS) of this sequence.
example 1:
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为4
example 2:
输入:nums = [0,1,0,3,2,3] 输出:4 解释:最长递增序列是 [0,1,2,3],因此长度为4
How do we define the and the state transition definition ?
1. Definition of state
First, we should split the problem, that is, define the sub-problem of this problem.
So, let's redefine this problem:
Given a sequence of numbers, the length is N,Let F~k~ be: the length of the longest increasing subsequence at the end of the k-
Find the maximum value from F~1~ to F~N~
Is the above definition the same as the original question?
Obviously the two are equivalent, but obviously in the second way of definition, we have found the sub-problem.
For F~k~, F~1~ to F~k-1~ are all sub-problems of F~k~.
these new problems ~ k ~ F called state .
F~k~ is the length of the LIS at the end of the kth item in the sequence. is the definition of the state.
2. Definition of state transition equation
After the state is defined, the relationship between the state and the state is called the state transition equation.
This question is based on the definition of F~k~:
Let F~k~ be: the length of the longest increasing subsequence at the end of the k-
Thinking about how to transfer between
split problem we said before No, here we can also use this trick, that is, split data .
What if there is only one number in the sequence? Then we should return 1 (we found the state boundary condition).
Then we can write the following state transition equation:
F~1~ = 1F~k~ = max ( F~i~ + 1 | i ∈(1,k-1))(k > 1)
That is: the length of the LIS ending with the k-th item is: max {the length of the LIS ending with the i-th item + 1 }, the i-th item is smaller than the k-th item
Everyone understands, is this the case~
Recall how we did it?
- We the problem (sub-problem) (definition of state) by splitting the problem
- Through the definition of the state boundary of the state, we have written the state and the transition between the 16076b6216b3d0 state, that is, the definition of the state transition equation
I wrote the state transition equation 16076b6216b401. , we have expressed the core idea of the dynamic programming
The rest is just to solve the recursive formula in memory.
Let's try to write the code below.
Code
First we define the dp array:
int[] dp = new int[nums.length];
(Note that the size of the dp array here is a bit different from the change exchange example in the previous article, that is, there is no +1 here. You can click here to read the previous article and understand it carefully.)
Then the meaning of the dp array here is:
dp[i] is the length of the longest increasing subsequence before the i bits of the given array.
So what is our initial state?
We know that the boundary conditions of the state are:
F~1~ = 1
- That is, if the data has only one bit, then 1 should be returned;
- When the number of data> 1, if the second increasing number does not appear in the entire sequence, then 1 is also returned.
Therefore, in the initial state, we assign 1 to each position in the dp array.
Arrays.fill(dp, 1);
Then, we traverse from the first element of the given array, that is, write the outer for loop:
for(int i = 0; i < nums.length;i++){
......
}
What do we do when we traverse to an element in the outer layer?
We have to find out whether there is a smaller number before this outer element. If it exists, then we update the dp[i]
If an element has a smaller number before it, doesn't this constitute an increasing subsequence?
So we can write the inner for loop:
for (int j = 0; j < i; j++) {
//如果前面有小于当前外层nums[i]的数,那么就令当前dp[i] = dp[j] + 1
if (nums[j] < nums[i]) {
//因为当前外层nums[i]前边可能有多个小于它的数,即存在多种组合,我们取最大的一组放到dp[i]里
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
At the end of the two-level loop, what is stored in the dp[] array is the maximum incremental subsequence length before the corresponding element position. We only need to traverse the dp[] array to find the maximum value to obtain the maximum incremental subsequence length of the entire array :
int res = 0;
for(int k = 0; k < dp.length; k++){
res = Math.max(res, dp[k]);
}
The code for this question has also been written, and the complete code is posted below:
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length < 2) return 1;
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for(int i = 0;i < nums.length;i++){
for(int j = 0;j < i;j++){
if(nums[j] < nums[i]){
dp[i] = Math.max(dp[i],dp[j] + 1);
}
}
}
int res = 0;
for(int k = 0;k < dp.length;k++){
res = Math.max(res,dp[k]);
}
return res;
}
}
The two-layer for loop of this question is basically the same as the code used to exchange change before. You can compare it with the previous article.
The difference is only the judgment condition of the inner for loop and the expression of the state transition equation (how to update dp[]), which is also the essence of dynamic programming.
summary
There are many misunderstandings and misunderstandings about dynamic programming. For example, the most common may be that it is space for time, and the difference between it and greedy is not clear.
I hope these two articles on dynamic programming can help you eliminate these misunderstandings, and better understand the essence of dynamic programming, state and state equations.
Of course, only these two articles want to will specifically explain some typical problems, such as knapsack problem, pebble game, stock problem, etc. , I hope to help you learn the algorithm Take fewer detours on the road.
If you have any algorithms and topic types you want to know, please leave a message in the comment area and let me know. See you in the next issue!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。