动态规划要解决的都是一些问题的最优解,即从很多解决问题的方案中找到最优的一个。当我们在求一个问题最优解的时候,如果可以把这个问题分解成多个子问题,然后递归地找到每个子问题的最优解,最后通过一定的数学方法对各个子问题的最优解进行组合得出最终的结果。总结来说就是一个问题的最优解是由它的各个子问题的最优解决定的。
在动态规划中,一般有以下几个要素:
- 状态转移方程。
- 状态。
- 最后一步。
- 初始条件。
其中,状态转移方程是动态规划的核心,它表示了原问题与子问题的相互关系;状态是用于保存各个子问题的解,它也是状态转移方程的y,一般而言,我们需要求出前面子问题的解,然后保存起来,因为后面的子问题会用到;最后一步,也就是从最后一步开始考虑,找到规律,这也是动态规划的突破口;初始条件,可以理解为最小子问题的状态,这是我们能够得到结果的基础。
&emap;下面通过几道题目来解释动态规划。
leetcode53:最大子序和
思路如下:
- 首先考虑最后一步,这里最后一步就是连续和最大子数组中最后一个元素索引是i;
- 然后明确状态,这道题要求的是连续和最大,那么其状态就是连续和,状态转移方程中的未知数就是i,和则用f(i)表示;
- 再考虑状态转移方程,一般而言,我们只需要考虑f(i)是如何从f(i-1)转移过来的,很显然,如果f(i-1)为负,那么f(i) = nums[i],如果f(i-1) > 0 ,那么f(i) = f(i-1) + nums[i],综合一下就是f(i) = max(f(i-1)+nums[i],f(i));
- 最后,再来考虑初始条件,很明显,这里是f(0) = nums[0]。
代码如下:
class Solution
{
public:
int maxSubArray(vector<int> &nums)
{
vector<int> f(nums.size(), 0); //用于保存状态
//初始状态
f[0] = nums[0];
//循环
int maxSum = f[0];
for (int i = 1; i < nums.size(); ++i)
{
f[i] = max(nums[i], f[i - 1] + nums[i]); //状态转移方程
maxSum = max(maxSum, f[i]);
}
return maxSum;
}
};
leetcode62:不同路径
思路如下:
- 本题思路使用动态规划。
- 首先从最后一步开始考虑,如果要到达(m-1,n-1),那么最后一步只能是(m-2,n-1)或者(m-1,n-2);
- 那么如何到达(m-2,n-1)及(m-1,n-2)就成了子问题。
- 综上所述,可以获得状态转移方程:f(i,j) = f(i-1,j)+f(i,j-1).其中f(i,j)表示要到达i,j的路径总数。
- 边界条件为f(0,0) = 1;其含义是站着不动即可到达,且只有这一种方案。
- 此外:当i = 1,或者j = 1时,可以发现f(i,j) = 1.表示只能直走或者竖着走。
代码如下:
class Solution
{
public:
int uniquePaths(int m, int n)
{
int f[m][n]; //m*n维数组,从0开始,所以最大索引为f[m-1][n-1].
f[0][0] = 1;
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
{
if (i == 0 || j == 0)
f[i][j] = 1;
else
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
return f[m-1][n-1];
}
};
leetcode300:最长递增子序列
这道题表面上看起来和最大子序和相似,但需要注意的是,f(i)的状态不一定能从f(i-1)转移过来。
思路如下:
- 这道题f(i)不一定能从f(i-1)中转移过来,因为i-1可能本身就不存在于最长递增子序列中。
- f(i)可能从f(j)中转移过来,其中0 =< j < i;
- 转移方程为:f(i) = max(f(j)) + 1。其中j >= 0 ,j < i;
- 但是要确保f(i)能够从f(j)转移过来,还要满足nums[i] >= nums[j];
代码如下:
class Solution
{
public:
int lengthOfLIS(vector<int> &nums)
{
int f[nums.size()];
f[0] = 1;
int maxRes = 1;
for (int i = 1; i < nums.size(); ++i)
{
f[i] = 1;
for (int j = 0; j < i; ++j)
{
if (nums[i] > nums[j])
{
f[i] = max(f[i], f[j] + 1); //如此循环,最后会找到f[j]中符合条件的最大值+1.
}
}
maxRes = max(maxRes, f[i]);
}
return maxRes;
}
};