问题描述如下:问题描述
动态规划问题的思维辅助工具就是列表。在使用列表之前需要首先知道动态规划所要使用的数组的每一个元素所表示的含义,在本题中dp数组的元素dp[i][j]的含义就是text1字符数组的前i个元素和text2的前j个元素的最大公共子序列的长度。则该dp数组的演化示例如下
a | b | c | d | e | |
---|---|---|---|---|---|
a | 1 | 1 | 1 | 1 | 1 |
c | 1 | 1 | 2 | 2 | 2 |
e | 1 | 1 | 2 | 2 | 3 |
动态规划思维辅助工具——列表可以帮组理解dp数组的变化过程,从而帮助找到dp的状态转移方程,才能写代码实现。
dp[i][j] | dp[i][j+1] | |
dp[i+1][j] | dp[i+1][j+1] |
状态转移方程的本质就是确定dp[i+1][j+1]与dp[i][j+1],dp[i+1][j]和dp[i][j]之间的关系。这就需要分类讨论:
动态规划的数组是可以在两个方面做优化的,对其优化的效果就是空间复杂度具有明显的提高。其具体优化可以分为两个思路。
这样的降维将n*m降至m的空间复杂度是有前提条件的。即:dp[i+1][j+1]的生成不依赖与全部依赖与这三个元素dp[i][j],dp[i+1][j]和 dp[i][j+1]时,可以通过题目修改遍历数组的顺序完成降维。
当dp[i+1][j+1]的生成依赖与上述三个元素时,则可以使用滚动数组的思路优化dp[N][M]为dp[2][M]。本题就可以使用这种优化,具体代码如下:
int longestCommonSubsequence(string text1, string text2) {
int length1 = text1.size();
int length2 = text2.size();
if(length1 == 0 || length2 == 0){
return 0;
}
vector<vector<int> > dp(2, vector<int>(length1+1, 0));
int idx = 0;
int jdx = 1;
for(int i = 0; i < length2; ++i){
jdx = (idx%2) == 0? 1 : 0;
for(int j = 0; j < length1; ++j){
if(text1[j] == text2[i]){
dp[idx][j+1] = dp[jdx][j] + 1;
}else{
dp[idx][j+1] = max(dp[jdx][j+1], dp[idx][j]);
}
}
idx = (idx%2 == 0? 1 : 0);
}
return dp[idx%2 == 0? 1 : 0][length1];
}
问题描述:问题描述
解题过程如下:
i\j | 10 | 9 | 2 | 5 | 3 | 7 | 101 | 18 |
---|---|---|---|---|---|---|---|---|
10 | 1 | |||||||
9 | 1 | |||||||
2 | 1 | 1 | ||||||
5 | 1 | 1 | 2 | |||||
3 | 1 | 1 | 2 | 2 | ||||
7 | 1 | 1 | 2 | 3 | 3 | |||
101 | 2 | 2 | 2 | 3 | 3 | 4 | ||
18 | 2 | 2 | 2 | 3 | 3 | 4 | 4 |
当遍历到数组的最后一个是,dp数组演化结束,dp数组中最大的值即为结果。
并不是所有的问题结果都是dp数组的最后一个元素,这样dp的设定属性有关,本题dp[i]的含义就是以num[i]结尾的最长上升子序列,而dp的最后一个元素的含义就是以num数组最后一个元素结尾的最长上升子序列,这并不一定是整个数组的最长上升子序列。
2. 求出状态转移方程
d p [ i ] = m a x ( d p [ j ] + 1 , d p [ i ] ) 0 < = j < i , w h e n n u m [ j ] < n u m [ i ] dp[i] = max(dp[j]+1, dp[i]) \space \space \space 0 <= j < i, \space when \space num[j] < num[i] dp[i]=max(dp[j]+1,dp[i]) 0<=j<i, when num[j]<num[i]
3. 写代码实现
int lengthOfLIS(vector<int>& nums) {
int length = nums.size();
if(length == 0)
return 0;
vector<int> dp(length, 1);
for(int i = 1; i < length; ++i){
for(int j = 0; j < i; ++j){
if(nums[i] > nums[j]){
dp[i] = max(dp[j] + 1, dp[i]);
}
}
}
return *max_element(dp.begin(), dp.end());
}
题目描述:题目描述
dp[i][j]的含义:i层时的,第j个元素对应的三角形路径和。
示例
2 2 2 3 4 3 \space \space 4 3 4 6 7 5 6 \space \space 7 \space \space 5 6 7 5 4 1 8 3 4\space \space1\space \space8\space \space3 4 1 8 3
动态规划表如下:
所在层\当前数组元素下标 | 0 | 1 | 2 | 3 |
---|---|---|---|---|
0 | 2 | 0 | 0 | 0 |
1 | 5 | 6 | 0 | 0 |
2 | 11 | 10 | 13 | 0 |
3 | 15 | 11 | 21 | 16 |
可以看出dp[i+1][j+1]=min(dp[i][j], dp[i][j+1])+num[i+1][j+1]。与dp[i+1][j]无关,所以,可以通过修改遍历顺序将dp有二维数组降至一维数组。由于dp[i+1][j+1]的生成与dp[i][j]有关,所以在求dp[i+1][j+1]之前不可以修改dp[i][j]所以需要倒序遍历。
代码如下:
int minimumTotal(vector<vector<int>>& triangle) {
int length = triangle.size();
if(length == 0)
return 0;
vector<int> dp(length , 0);
dp[0] = triangle[0][0];
for(int i = 1; i < length; ++i){
for(int j = i; j >= 0; --j){
if(j == i){
dp[j] = dp[j-1];
}else if(j != 0 ){
dp[j] = min(dp[j-1], dp[j]);
}
dp[j] += triangle[i][j];
}
}
return *min_element(dp.begin(), dp.end());
}
题目描述:题目描述
dp[i]:以num[i]结尾的最大子序和。
以[-2,1,-3,4,-1,2,1,-5,4]为例,动态表如下:
-2 | 1 | -3 | 4 | -1 | 2 | 1 | -5 | 4 | |
---|---|---|---|---|---|---|---|---|---|
0 | -2 | ||||||||
1 | 1 | ||||||||
2 | -2 | ||||||||
3 | 4 | ||||||||
4 | 3 | ||||||||
5 | 5 | ||||||||
6 | 6 | ||||||||
7 | 1 | ||||||||
8 | 5 |
动态方程:dp[i+1] = max(dp[i] + num[i+1], num[i]);
优化:有动态方程可以看出,dp[i+1]仅与dp[i]有关,所以,可以将dp数组使用两个变量表示,pre和max_ele,分别记录dp[i]的值求出dp[i+1],max_ele记录dp中最大的数。
代码如下:
int maxSubArray(vector<int>& nums) {
int length = nums.size();
if(length == 0)
return 0;
int pre = nums[0];
int max_ele = pre;
for(int i = 1; i < length; ++i){
pre = max(nums[i] + pre , nums[i]);
if(max_ele < pre)
max_ele = pre;
}
return max_ele;
}
本周总结:
动态规划的思考步骤: