在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。
-
基本思想
问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,在构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。
-
使用条件
通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
-
例题
①最小路径和给定一个包含非负整数的m*n网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
解:
1、状态设置,以二维数组dp[i][j]保存过程中从左上角到当前位置最小和
2、建立状态转换方程,由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。
创建二维数组dp,与原始数组grid的大小相同,dp[i][j] 表示从左上角出发到 (i,j)(i,j) 位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于 dp 中的其余元素,通过以下状态转移方程计算元素值。
当i>0 且 j=0 时,dp[i][0]=dp[i−1][0]+grid[i][0]。
当i=0 且j>0 时,dp[0][j]=dp[0][j−1]+grid[0][j]。
当 i>0 且j>0 时,dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]。
最后得到dp[m−1][n−1] 的值即为从网格左上角到网格右下角的最小路径和。
dp数组变化
int minPathSum(int** grid, int gridSize, int* gridColSize){
int rows = gridSize, columns = gridColSize[0];
if (rows == 0 || columns == 0) {
return 0;
}
int dp[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = fmin(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
②最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
解:
1、状态设置,创建dp数组存放从开头到当前位置的最长子序列场长
2、建立状态转换方程,我们从小到大计算 dp[] 数组的值,在计算 dp[i] 之前,我们已经计算出 dp[0…i−1] 的值,则状态转移方程为:
dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i]
dp数组变化过程:
int lengthOfLIS(int* nums, int numsSize){
if (numsSize <= 1) return numsSize;
int lis[numsSize];
int length = 1, index = 0;
lis[index] = nums[0];
for (int i = 1; i < numsSize; i++) {
if (nums[i] > lis[index]) {
lis[++index] = nums[i];
length++;
}
else if (nums[i] < lis[index]) {
if (index == 0) lis[index] = nums[i];
else {
for (int j = index; j >= 0; j--) {
if (j == 0 || lis[j - 1] < nums[i]) {
lis[j] = nums[i];
break;
}
}
}
}
}
return length;
}