动态规划是一种运筹学的思想,体现在程序上面就是通过状态转移方程穷举出所有的情况。
状态转移方程的设定思想类似于数学证明上的“数学归纳法”,假设结果是正确的,假设的条件也是正确的,通过假设的条件和结果,推导出结论。说白了,状态转移方程是F(i)=F(i-1)+当前操作;当前的结果,仅仅取决于上一步的结果和本次的决定。
解决动态规划问题中的 dp table(动态规划表) 是一种常用的解法,(并不能保证适用于每一道题)。
dp table 一般有一维数组或者二维数组。要看具体的问题,其实数组的作用就是保存上一次的产生出的结果,方便根据动态转移方程推出当前这一步的结果。
要产生这个dp table ,首先要简化问题,可以先从最简单的例子考虑,设为F(0),根据F(0)求出F(1)以此类推。
举几个例子吧:
链接:https://www.nowcoder.com/questionTerminal/585d46a1447b4064b749f08c2ab9ce66
来源:牛客网
对于一个数字序列,请设计一个复杂度为O(nlogn)的算法,返回该序列的最长上升子序列的长度,这里的子序列定义为这样一个序列U1,U2...,其中Ui < Ui+1,且A[Ui] < A[Ui+1]。
给定一个数字序列A及序列的长度n,请返回最长上升子序列的长度。
测试样例:
[2,1,4,3,1,5,6],7
返回:4
忽略复杂度的要求,使用一维数组。
初始化dp数组的值为1 ,下标为0的元素的最长递增子序列就是只有本身1个,F(1)怎么办呢,我们发现,元素4只要在它之前的所有元素中,找到比它本身小的元素,再加上4本身,不就又构成一个新的子序列了吗。
好了我们的转移方程已经找到了,就是
vector<int> dp(A.size(), 1);
for (int i = 0; i < A.size(); ++i)
{
for (int j = 0; j < i; ++j)
{
if (A[j] < A[i])
{
dp[i] = max(dp[j] + 1, dp[i]);
}
}
}
有了状态转移方程,题就解完了,使用的dp table是一维数组。
class AscentSequence {
public:
int findLongest(vector<int> A, int n) {
write code here
vector<int> dp(A.size(), 1);
for (int i = 0; i < A.size(); ++i)
{
for (int j = 0; j < i; ++j)
{
if (A[j] < A[i])
{
dp[i] = max(dp[j] + 1, dp[i]);
}
}
}
int ret = INT_MIN;
for (auto e : dp)
{
ret = max(ret, e);
}
return ret;
}
};
最小路径和:
链接:https://www.nowcoder.com/questionTerminal/2fb62a4500af4f4ba5686c891eaad4a9
来源:牛客网
给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。
输入描述:
第一行输入两个整数 n 和 m,表示矩阵的大小。 接下来 n 行每行 m 个整数表示矩阵。
示例1
输入
4 4 1 3 5 9 8 1 3 4 5 0 6 1 8 8 4 0
输出
12
使用二维dp table 只能向左或者向右走,转移方程很好写就是:
上面或者左面的较小值加上本位的值,使用二维数组记录当前值和上一步的值更加方便,空间换时间的做法。
class Solution
{
public:
int minPathSum(vector<vector<int> > &grid)
{
//先算出第一行和第一列的特殊的
int row = grid.size();//行
int len = grid[0].size();//列
int i = 0;
int j = 0;
vector<vector<int>> arry(row, vector<int>(len, 0));
arry[0][0] = grid[0][0];
int tmp=grid[0][0];
for ( i=1;i<len;++i)
{
//行
tmp= tmp+grid[0][i];
arry[0][i] = tmp;
}
tmp = grid[0][0];
for ( i=1;i<row;++i)
{
//列
tmp = tmp + grid[i][0];
arry[i][0] = tmp;
}
/*for (auto &e : arry[0])
{
cout << e << endl;
}
for (auto &e : arry[2])
{
cout << e << endl;
}*/
//取上面和左面的最小值,加上它自己。
for ( i=1;i<row;++i )
{
for (j=1;j<len;++j)
{
arry[i][j] = min(arry[i - 1][j], arry[i][j - 1]) + grid[i][j];
}
}
return arry[row - 1][len - 1];
}
};