Dynamic Programming Basics
DP for short , if a problem has many overlapping sub-problems , it is most effective to use dynamic programming.
Each state in dynamic programming must be derived from the previous state
Question making process:
1. Determine the meaning of the dp array ( dp table ) and the subscript2. Determine the recursive formula3. How to initialize the dp array4. Determine the traversal order5. Example derivation dp array
509. Fibonacci numbers
509. Fibonacci Numbers - LeetCode https://leetcode.cn/problems/fibonacci-number/
First think about the meaning of the dp array:
dp[i] refers to the value of the i-th number in the array
State transition equation: dp[i]=dp[i-1]+dp[i-2]
Initialization: the initial 1st and 2nd numbers
Determine the traversal order from the beginning to the nth
Example derivation dp[i] = dp[i - 1] + dp[i - 2]
class Solution {
public:
int fib(int N) {
if (N <= 1) return N;
vector<int> dp(N + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= N; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[N];
}
};
70. Climb the stairs
70. Climbing Stairs - LeetCode https://leetcode.cn/problems/climbing-stairs/
First think about the meaning of the dp array:
dp[i] means that there are several ways to climb to the i-th level
dp[i]=dp[i-1] + dp[i-2]
Initialization: the initial 1st and 2nd numbers
Determine the traversal order from the beginning to the nth
Example derivation dp[i] = dp[i - 1] + dp[i - 2]
class Solution {
public:
int climbStairs(int n) {
if(n<=2){
return n;
}
vector<int> vec(n+1,0);
vec[1]=1;
vec[2]=2;
for(int i=3;i<=n;i++){
vec[i]=vec[i-1]+vec[i-2];
}
return vec[n];
}
};
746. Climb Stairs with Minimum Cost
dp[i] represents the minimum cost to reach the i-th step
dp[i]=min(dp[i-1],dp[i-2])+cost[i];
Initialize dp[0]=cost[0] dp[1]=cost[1]
The order starts from the second step
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size(),0);
//初始化数组
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i<cost.size();i++){
dp[i]=min(dp[i-1],dp[i-2])+cost[i];
}
return min(dp[cost.size()-1],dp[cost.size()-2]);
}
};
62. Different paths
62. Different Paths - LeetCode https://leetcode.cn/problems/unique-paths/
This question needs to record the number of paths that can be reached for each location
dp[i][j] represents the path to the point at row i, column j
dp[i][j]=dp[i-1][j]+dp[i][j-1] Each position can be obtained by moving forward from the upper and left positions
Initialization obviously dp[i][0]=1 dp[0][j]=1 means that the arrival paths of the first row and the first column are both 1
The order of traversal traverses from each layer layer by layer
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 1));
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
63. Different Paths II
63. Unique Paths II - LeetCode https://leetcode.cn/problems/unique-paths-ii/
Compared with the previous question, the ontology has more roadblocks in the middle
When initializing the map, we need to identify the obstacles
For each point, you can come through the upper or left node
State transition equation: grid[i][j]=grid[i-1][j]+grid[i][j-1]
Initialization: each element in the first row and first column is initialized to 1
Sequence: Traversing each line continuously downwards
Finally return grid[obstacledGrid.size()][obstacleGrid[0].size()]
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int n=obstacleGrid.size();
int m=obstacleGrid[0].size();
vector<vector<int>> grid(n,vector<int>(m,0));
//初始化数组 第一行和第一列可以到达的路径都是1
for(int i=0;i<n&&obstacleGrid[i][0]==0;i++) grid[i][0]=1;
for(int j=0;j<m&&obstacleGrid[0][j]==0;j++) grid[0][j]=1;
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(obstacleGrid[i][j]==1) continue;
grid[i][j]=grid[i-1][j]+grid[i][j-1];
}
}
return grid[n-1][m-1];
}
};
343 integer split
343. Integer Break - LeetCode https://leetcode.cn/problems/integer-break/
dp[i] represents the largest product of i split
Recursion formula (state transition formula): traverse the previous dp data dp[i]=max(dp[i],max(dp[ij]*j,j*(ij)));
dp[i] is very small (acting as an iteration), so the maximum value is basically the value in the latter max (dp[ij] represents the maximum product that ij is split into, if the tree is multiplied by j, then Continuously loop, until the case of i-1, you can find out the maximum value of i split)
Initialization, for 0 and 1 cannot be split, then the largest product is 0
Sequence: starting from 1, from front to back
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1,0);
dp[2]=1;
for(int i=3;i<=n;i++){
for(int j=1;j<i-1;j++){
dp[i]=max(dp[i],max(dp[i-j]*j,j*(i-j)));
}
}
return dp[n];
}
};
96. Different Binary Search Trees
dp[n] One-dimensional array, thinking about meaning i means the number of binary search trees that i elements can form
State transition: dp[i]+=dp[j-1][ij] (j ranges from 1 to i)
Initially consider the case where i is 0: a search tree composed of 0 elements can be directly regarded as a binary search tree dp[0]=1
Order: from the first node until the nth node
return dp[n];
Why is it multiplication, because the left and right subtrees are equivalent to different selection steps
Therefore, the number of final results should be the product of the two
class Solution {
public:
int numTrees(int n) {
//dp[i]为i个节点时,二叉搜索树的中枢
vector<int> dp(n+1);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
dp[i]+=dp[j-1]*dp[i-j];
}
}
return dp[n];
}
};
knapsack problem
The knapsack problem is a classic problem of dynamic programming, including 01 knapsack, complete knapsack, multiple knapsack and other sub-problems
0-1 knapsack problem
There are N items and a knapsack that can be weighed up to W. The weight of the i- th item is weight[i] , and the resulting value isvalue[i] . Each item can only be used once , and find out which items are put into the backpack with the largest total value.To sum up, it is to put the given items into the backpack and find the maximum value that can be put into it.
Define an array int dp[i][j], i is the same as the number of items and j is the maximum capacity of the backpack
dp[i][j] is the maximum value that can be obtained by putting the first i items in a reasonable capacity when the capacity is j
转移公式: dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
Initialization: For j, j<value[0] is 0, if j>value[0], it is value[0];
int main(){
vector<vector<int>> dp(weight.size()+1,vector<int>(rongliang+1,0));
//全部初始化为0
for(int j=value[0];j<=rongliang;j++){
dp[0][j]=value[0];
}
for(int i=1;i<weight.size();i++){
for(int j=0;j<=rongliang;j++){
if(j<weight[i]) dp[i][j]=dp[i-1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j-weight[i]] + value[i]);
}
}
cout << dp[weight.size() - 1][bagWeight] << endl;
}
#include<iostream>
#include<vector>
using namespace std;
int main() {
int sum, rongliang;
cin >> sum >> rongliang;
vector<int> tiji(sum+1, 0);
vector<int> jiazhi(sum+1, 0);
for (int i = 1; i <= sum; i++) {
cin >> tiji[i] >> jiazhi[i];
}
//初始化dp数组
vector<vector<int>> dp(sum + 1, vector<int>(rongliang + 1, 0));
//进行动态规划
for (int i = 1; i <= sum; i++) {
for (int j = rongliang; j >= 0; j--) {
//状态转移方程
if (j >= tiji[i]) {
//装得下
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - tiji[i]] + jiazhi[i]);
}
else {
//装不下
dp[i][j] = dp[i - 1][j];
}
}
}
cout << dp[sum][rongliang];
return 0;
}
These two codes are different:
First: the start positions of the value and weight arrays are different
Second: In the second round of the loop, the second program goes from big to small
The starting position is different, resulting in a different initialization process, the second one does not need to be initialized, and the initialization process is included in the loop
Because the value of dp is transferred from the state of the previous line, then we don’t care about the order of traversal, so it is the same from the beginning to the end, and from the end to the beginning, we can still start from the capacity and continue to decrease
Think about how to simplify the solution to the problem
Using a bit dynamic array can solve the problem
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
If the layer of dp[i-1] is copied to the i layer, the expression can be dp[i][j]=max(dp[i][j],dp[i][j-weight[ i]]+value[i]);
Just use a 1D array and just use dp[j]
416. Divide Equal Sum Subsets
416. Partition Equal Subset Sum - LeetCode https://leetcode.cn/problems/partition-equal-subset-sum/
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
vector<int> dp(10001,0);
for(int i=0;i<nums.size();i++){
sum+=nums[i];
}
if(sum%2!=0) return false;
int target=sum/2;
for(int i=0;i<nums.size();i++){
for(int j=target;j>=nums[i];j--){
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[target]==target) return true;
return false;
}
};
Split the subset and find the same subset as
Divide in half to find the one with the largest capacity;
The meaning of the dp array, when the capacity is i, get the maximum value of the sum of array elements
dp[i]=max(dp[i],dp[i-nums[i]]+nums[i])
Initialization, when the capacity is less than the smallest array element, dp is zero, so the initialization is all zeros
1049. The Weight of the Last Stone II
1049. The Weight of the Last Stone II - LeetCode https://leetcode.cn/problems/last-stone-weight-ii/
class Solution { public: int lastStoneWeightII(vector<int>& stones) { int sum=0; for(int i=0;i<stones.size();i++){ sum+=stones[i]; } vector<int> vec(150001,0); int target=sum/2; for(int i=0;i<stones.size();i++){ for(int j=target;j>=stones[i];j--){ vec[j]=max(vec[j],vec[j-stones[i]]+stones[i]); } } return sum-vec[target]-vec[target]; } };
Think about this question:
the total number is divided into half, and the possibility of obtaining the maximum capacitysum-dp[target]-dp[target];
sum-dp[target] must be greater than dp[target]
494. Target and
494. Target Sum - LeetCode https://leetcode.cn/problems/target-sum/
Add a +- sign in front of the number to get the number of the target sum
It is to hold enough things of a specific capacity. There are several ways to install them in total.
dp[j]+=dp[j-nums[i]]
The order of traversal is the same as the 01 knapsack problem
Initialization: Through the state transition equation, it can be concluded that when it is 0, it means that there are several options when the result is 0
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(auto i:nums){
sum+=i;
}
if(target>sum) return 0;
if((target+sum)%2==1) return 0;
int nice=(target+sum)/2;
vector<int> vec(1001,0);
vec[0]=1;
for(int i=0;i<nums.size();i++){
for(int j=nice;j>=nums[i];j--){
vec[j]+=vec[j-nums[i]];
}
}
return vec[nice];
}
};
322 change change
322. Change Exchange - LeetCode https://leetcode.cn/problems/coin-change/
The meaning of dynamic programming array: dp[i] represents the minimum number of coins required to get the total amount of i
dp[i] is the smallest of all dp[i - coins[j]] + 1
Recursion formula: dp[i]=min(dp[i-coins[j]]+1,dp[i])
How to initialize the array: the number of coins whose total amount is 0 must be 0, then dp[0]=0
Other dp[i] must be initialized to the maximum value
Determine the order of traversal: find the number of combinations, the size of the inner traversal backpack
Find the number of permutations, the outer layer traverses the size of the backpack
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//设置dp数组
vector<int> dp(amount+1,INT_MAX);
//初始化dp数组
dp[0]=0;
//遍历背包
for(int i=0;i<=amount;i++){
for(int j=0;j<coins.size();j++){
if(i>=coins[j] && dp[i-coins[j]]!=INT_MAX)
dp[i]=min(dp[i-coins[j]]+1,dp[i]);
}
}
if (dp[amount] == INT_MAX) return -1;
return dp[amount];
}
};
139 word split
string split
139. Word Breaking - LeetCode (LeetCode) https://leetcode.cn/problems/word-break/backtracked code:
Word backtracking is split and searched, if it can be found, it will return true, if all the results cannot be found, it will return false
Direct violent backtracking, the recursion will time out, you need to use the memory array to record the previous results
The ac code is as follows:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
vector<int> memory(s.size()+1,-1);
return backtrace(s,wordSet,memory,0);
}
bool backtrace(string s,unordered_set<string>& wordDict,vector<int>& memory,int startindex){
if(startindex>=s.size()){
return true;
}
if(memory[startindex]!=-1) return memory[startindex];
//如果已经被拆分过了就是用拆分过的值
for(int i=startindex;i<s.size();i++){
string word=s.substr(startindex,i-startindex+1);
if(wordDict.find(word)!=wordDict.end() && backtrace(s,wordDict,memory,i+1)){
memory[startindex]=1;// 记录以startIndex开始的⼦串是可以被拆分的
return true;
}
}
memory[startindex]=0; // 记录以startIndex开始的⼦串是不可以被拆分的
//为什么不能再次拆分,因为之前的代码已经将这个起始位置后面的字符串进行了分割
return false;
}
};
The dynamic programming solution:
dp[i]: If the string length is i, dp[i] is true, indicating that one or more words that appear in the dictionary can be split
The recursive formula is if ( the substring of [j, i] appears in the dictionary && dp[j] is true) then dp[i] = trueInitialization of dp array, dp[0] is true, dp[0] is the basis of recursion, dp[0] is trueTraversal order: Combination or permutation is fine, it is best to choose the size of the outer recursive backpack and the inner loop items
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(),wordDict.end());
vector<bool> dp(s.size()+1,false);
dp[0]=true;
for(int i=1;i<=s.size();i++){
for(int j=0;j<i;j++){
string word=s.substr(,i-j);
if (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
198 Robbery
198. House Robbery - LeetCode https://leetcode.cn/problems/house-robber/
The family knot problem is a classic dynamic programming:
dp[i] represents the maximum amount that the previous i family can steal
State transition equation: dp[i] can be deduced from dp[i-1] and dp[i-2]. If you want to steal the i-th house, you can’t steal the i-1th house, or don’t steal the i-th house
因此dp[i]=max(dp[i-1],dp[i-2]+nums[i])
Initialization: dp[2] needs to use dp[0] and dp[1] at the beginning. Therefore, initialize dp[0] to nums[0], dp[1]=max(nums[0],nums[1])
Traversal order: front to back
ac's code:
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> dp(nums.size(),0);
if(nums.size()==0) return 0;
if(nums.size()==1) return nums[0];
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<nums.size();i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
121 Buying and selling stocks (buy and sell once)
A classic dynamic programming problem:
It can be solved greedily, we need to find out the minimum cost and the maximum selling price, and ensure that the buying time is before selling
int min=1e9;
int profit=0;
for(int i=0;i<prices.size();i++){
if(prices[i]<min){
min=prices[i];
}
profit=prices[i]-min>profit?prices[i]-min:profit;
}
return profit;
The practice of dynamic programming:
Dynamic programming table: dp[i][0] the cash in hand when i holds it, dp[i][1] the largest cash when it is sold on i day
State transition equation:
dp[i][0]=max(dp[i-1][0],-prices[i])
dp[i][1]=max(dp[i-1][1],prices[i]+dp[i-1][0]);
Initialization: From the state transition equation, it can be seen that dp[0][0] and dp[0][1] need to be initialized
Traversal order: front to back
if(prices.size()==1) return 0;
vector<vector<int>> dp(prices.size(),vector<int>(2,0));
dp[0][0]=0-prices[0];
dp[0][1]=0;
for(int i=1;i<prices.size();i++){
dp[i][0]=max(dp[i-1][0],0-prices[i]);
dp[i][1]=max(dp[i-1][1],prices[i]+dp[i-1][0]);
}
return dp[prices.size()-1][1];
122 Buying and selling stocks (buying and selling multiple times)
Compared with the previous question, this question can be bought and sold multiple times:
dp[i][0] and dp[i][1] have the same meaning as before
State transition equation:
dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
The key lies in the transformation of the state transition equation
The order of traversal is from front to back
ac code:
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size(),vector<int>(2,0));
if(prices.size()==1) return 0;
dp[0][0]=0-prices[0];
dp[0][1]=0;
for(int i=1;i<prices.size();i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
//可能在上一次将股票卖出之后还是会存在一些现金在手中
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.size()-1][1];
}
};
300 longest increasing subsequence
The key to this question is that dp[i] can be derived through dp[j] (j<i)
dp[i]=max(dp[i],(nums[j]<nums[i]?dp[j]+1:dp[j]); j belongs to [0-i-1],dp[i] The meaning is the longest incremental subsequence that can be formed using the first i elements. Initialization, when there is only one element, the size of the incremental subsequence is 1, and the order of traversal is from front to back
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size()==1) return nums.size();
vector<int> dp(nums.size(),1);
int max_num=0;
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]) dp[i]=max(dp[j]+1,dp[i]);
max_num=max(max_num,dp[i]);
}
}
return max_num;
}
};
674 Longest Continuous Increasing Subsequence
The main idea of this question is to need continuous increment
dp[i] represents the length of the longest incremental subsequence that can be formed by the first i elements
dp[i]=max(nums[i]>nums[i-1]?dp[i-1]+1:0,dp[i]);
The order of traversal is from forward to
Initialize ditto
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
vector<int> dp(nums.size(),1);
if(nums.size()==1) return nums.size();
int max_num=0;
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]) dp[i]=max(dp[i-1]+1,dp[i]);
max_num=max(max_num,dp[i]);
}
return max_num;
}
};
718 Longest Repeating Subarray
The state of dp[i][j] of this question can be introduced by dp[i-1][j-1]
Initialization: dp[0][0] represents the value of the longest common subsequence whose end array subscript is -1, -1, dp[i-1][0],dp[0][j-1]
There is no special meaning, dp[0][0] is initialized to 0 for better derivation, dp[i-1][0], dp[0][j-1] are also initialized to 0
The result is traversed from front to back
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
int max_num=0;
for(int i=1;i<=nums1.size();i++){
for(int j=1;j<=nums2.size();j++){
if(nums1[i-1]==nums2[j-1]) dp[i][j]=dp[i-1][j-1]+1;
max_num=max(max_num,dp[i][j]);
}
}
return max_num;
}
};
Find the longest common substring of two strings
Dynamic programming problem:
The meaning represented by dp[i][j] is the length of the largest common substring among the first i characters of s1 and the first j characters of s2State transition equation: if(s1[i],s2[j])dp[i][j]=dp[i-1][j-1]+1
Recursive order: Exchange the contents of the two strings at the beginning, traversing from front to back
#include<iostream>
#include<vector>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
if(s1.size()>s2.size())
swap(s1, s2);
int len1 = s1.size();
int len2 = s2.size();
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
int start = 0;
int max = 0;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] >= max) {
max = dp[i][j];
start = i - max;
}
}
}
cout << s1.substr(start, max);
}
1143 longest common subsequence
1143. Longest Common Subsequence - LeetCode https://leetcode.cn/problems/longest-common-subsequence/
dp[i][j] represents the longest common subsequence among the first i characters of string1 and the first j characters of string2
State transition formula: if string[i-1]==string2[j-1] then dp[i][j]=dp[i-1][j-1]+1
If not the same, then dp[i][j] takes the maximum value of dp[i,j-1] and dp[i-1][j]
Order of recursion: from front to back, two loops
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
if(text1.size()>text2.size()){
swap(text1,text2);
}
int max_flag=0;
vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));
for(int i=1;i<=text1.size();i++){
for(int j=1;j<=text2.size();j++){
if(text1[i-1]==text2[j-1]){
dp[i][j]=dp[i-1][j-1]+1;
}else
{
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
}
}
}
return dp[text1.size()][text2.size()];
}
};