动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
视频教程上以斐波那契数列为例,教先递归(复杂度指数增长),再找重复计算的部分,替换为记忆搜索法(自顶向下,复杂度O(2n-1)),再转换为自底向上的动态规划(复杂度O(n));但是自我感觉看下面例题程序更为易懂,就是从下向上或者从左向右的递推。
- leetcode 第120题 Triangle
给定一个三角形数字阵列,选择一条自顶向下的路径,使得沿途所有数字之和最小,每一步只能移动到相邻的格子中。
[2],
[3,4],
[6,5,7],
[4,1,8,3]
例子最小值为11(i.e., 2 + 3 + 5 + 1 = 11). Note: 设定计算复杂度为O(n) .
class Solution {
public:
int minimumTotal(vector<vector<int> > &triangle) {
int i,j;
for(i=triangle.size()-2;i>=0;i--)//vector下标从0开始,所以对于4行的,第三行的下标为2
{
for(j=0;j<i+1;j++)
{
triangle[i][j]+=min(triangle[i+1][j],triangle[i+1][j+1]);
}
}
return triangle[0][0];
}
};
//金字塔自下向上,逐层递推上去
- 第64题 Minimum path sum
给一个m*n矩阵,其中每一个格子包含一个非负整数,寻找一条从左上角到右下角的路线,使得沿路的数字和最小。每一步只能左移或者下移。
class Solution {
public:
int minPathSum(vector<vector<int> > &grid) {
int m=grid.size();
int n=grid[0].size();
for(int i = 0; i < m; i++)
{
for(int j=0;j<n;j++)
{
if(i==0&&j!=0) grid[i][j]+=grid[i][j-1]; //第一行的值没有上方值,只加左方值更新自己
if(i!=0&&j==0) grid[i][j]+=grid[i-1][j]; //第一列的值没有左方值,只加上方值更新自己
if(i!=0&&j!=0)
{
grid[i][j]+=min(grid[i][j-1],grid[i-1][j]);
}
}
}
return grid[m-1][n-1];
}
};
//从左向右更新矩阵,每个位置的值等于 原值+上方或左方的较小值。
- (leetcode343)Integer Break
给定一个正数n,可以将其分割成多个数字的和,若要让这些数字的乘积最大,求分割方法(至少要分成两个数)。算法返回这个最大的乘积。
如n=2,返回1,2=1+1
n=10,返回36, 10=3+3+4
(1) 递归法(自顶向下,确定出树的结构)
#include <iostream>
using namespace std;
int ibreak(int n){
int product =0;
if ( n == 1 ) return 1;
if ( n == 2 ) return 2;
for ( int i=1; i<n; i++)
{
//注意得比较上i*(n-i),以决定是否进一步划分下去,
product = max( max(i*(n-i), i*ibreak(n-i)), product);
}
return product;
}
int main() {
int n = 2;
cout << ibreak(n) <<endl;
return 0;
}
(2)记忆化搜索的方法
将递归中的重复计算的值,存到一个动态数组里,然后将递归转化为记忆化搜索。实际上就是添加库,定义、初始化动态数组,按照递归过程为动态数组赋值。
#include <iostream>
#include <cassert>
#include<vector>
using namespace std;
class Solution{
public:
int ibreak(int n){
vector<int> memo;
memo = vector<int> (n+1,-1);
if ( n == 1 ) return 1;
if ( n == 2 ) return 2;
if ( memo[n] != -1) //当已计算过memo[n],直接返回memo值
return memo[n];
int product=0;
for ( int i=1; i<n; i++)
{
product = max( max(i*(n-i), i*ibreak(n-i)), product);
}
memo[n] = product;
return memo[n];
}
};
int main() {
int n = 10;
Solution a; //调用类内函数,需要先定义类的对象
cout <<a.ibreak(n)<<endl;
return 0;
}
(3)动态规划的解法(双重循环)
自底向上,先计算最底层的,递推上去。所以先计算n=1时,然后使用双层循环为memo【i】赋值,计算到n。
#include <iostream>
#include <cassert>
#include<vector>
using namespace std;
class Solution{
public:
int ibreak(int n){
vector<int> memo;
memo = vector<int> (n+1,-1);
//动态规划为自底向上递推,先计算n=1
memo[1]=1;
for( int i=2; i<=n; i++){
//求解memo[i]
for( int j=1; j<i; j++){
//每一个i=j+(i-j)
memo[i] = max(memo[i],max(j*memo[i-j],j*(i-j)));
}
}
return memo[n];
}
};
int main() {
int n = 10;
Solution a; //调用类内函数,需要先定义类的对象
cout <<a.ibreak(n)<<endl;
return 0;
}
- (279) Perfect Squares
给出一个正整数n,寻找最少的完全平方数,使他们的和为n。完全平方数:1,4,9,16…, 12=4+4+4, 输出3;13=4+9,输出2
(1)递归法
n
n-1 n-4 …
n-2 n-5 n-5 n-8 … 每一个n下一步可以分为 1+(n-1), 4+(n-4), …t^2 + (n-t方)共t种可能,可以使用递归逐步向下
#include <iostream>
#include <cassert>
#include <cmath>
using namespace std;
class Solution{
public:
int PSquare(int n){
int t = floor(sqrt(n));
int num = 65536;
if (n == 0) return 0;
if (n == 1) return 1;
for(int i = 1; i <= t; i++)
{
//n分解为,1个1和PSquare(n-1), || 1个4和PSquare(n-4) ||....依次遍历保留最小
num = min (num, 1+PSquare(n-pow(i,2)));
}
return num;
}
};
int main() {
int n = 12;
Solution a;
cout <<a.PSquare(n)<<endl;
return 0;
}
(2)记忆化搜索法
在递归的基础上,将num改为memo[], 循环前加一个判断,是不是计算过
#include <iostream>
#include <cassert>
#include <cmath>
#include<vector>
using namespace std;
class Solution{
public:
int PSquare(int n){
vector <int> memo;
memo = vector<int> (n+1,65536);
int t = floor(sqrt(n));
// int num = 65536;
if (n == 0) memo[n]=0;
if (n == 1) memo[n]=1;
if (memo[n] != 65536) return memo[n];
for(int i = 1; i <= t; i++)
{
memo[n] = min (memo[n], 1+PSquare(n-pow(i,2)));
}
return memo[n];
}
};
int main() {
int n = 12;
Solution a; //调用类内函数,需要先定义类的对象
cout <<a.PSquare(n)<<endl;
return 0;
}
(3)动态规划
在记忆化搜索的基础上,改为自底向上。
#include <iostream>
#include <cassert>
#include <cmath>
#include<vector>
using namespace std;
class Solution{
public:
int PSquare(int n){
vector <int> memo;
memo = vector<int> (n+1,65536);
if (n == 0) memo[n]=0;
if (n == 1) memo[n]=1;
if (memo[n] != 65536) return memo[n];
for(int i = 2; i <= n; i++) //动态规划就是在外层再加一个外循环,求n改为求i,别忘了把t移进来
{
//求memo[i]
int t = floor(sqrt(i));
for(int j=1; j<=t; j++)
{
memo[i] = min (memo[i], 1+PSquare(i-pow(j,2)));
}
}
return memo[n];
}
};
int main() {
int n = 12;
Solution a;
cout <<a.PSquare(n)<<endl;
return 0;
}