一、动态规划问题的一般解决过程
1、动归介绍
1)动归的定义
动态规划是分治思想的延伸,通俗来说就是”大事化小,小事化无“。
再将大问题化解为小问题的分治过程中,保存这些小问题已经处理好的结果供后面处理更大问题时使用这些结果。
2)动归的特点
- 把原来的问题分解成了几个相似的子问题
- 所有的子问题都只需要解决一次
- 存储子问题的解
3)动态规划的本质
对问题状态的定义以及状态转移方程的定义(状态以及状态之间的递推关系)。
2、动态规划解题过程
动态规划问题一般从以下四个角度考虑:
- 状态定义
- 状态之间的转移方程
- 状态的初始化
- 返回结果
状态定义的要求:定义的状态一定要形成地推关系。
3、适合用动态规划解决的问题
- 最大最小值问题
- 是否可行问题
- 方案个数
二、动态规划的题目
1、菲波那切数列
1)问题分析
斐波那切数列是一个非常典型的动态规划题目,在斐波那切数列中除第0、1项外其余项都是前两项的和。求解斐波那切数列第n项和最多的解法就是递归,如下:
递归思路:每一项都是前两项的和,求第i项求出第i-1和第i-2项即可。当i = 0或i = 1时,直接返回0。可以写出如下代码
int fib(int n)
{
if(n <= 1)
return n;
return fib(n-1)+fib(n-2);
}
对上述代码进行分析:
动态规划思想:将大问题分解成小问题,保存小问题的解,在计算大问题时使用小问题的解。
解题思路:
- 状态定义:斐波那切数列的第i项f(i)。
- 状态之间的转换方程:f(i) = f(i-1) + f(i-2) (题目已知条件条件)
- 状态的初始结果:f(0) = 0,f(1) = 1; (题目已知条件)
- 返回结果:f(i)
2)代码描述
class Solution {
public:
int Fibonacci(int n) {
if(n <= 1)
return n;
int fn1 = 1;//f(n-1)初始状态为f(1) = 1
int fn2 = 0;//f(n-2)初始状态f(0) = 0
int fn;//返回结果
for(int i = 2;i <= n;i++)
{
fn = fn1+fn2;//状态转移方程f(n) = f(n-1) + f(n-2)
fn2 = fn1;//状态改变
fn1 = fn;
}
return fn;
}
};
2、变态青蛙跳台阶
1)问题分析
- 状态定义:青蛙跳上i级台阶的所有跳法f(i)
- 状态之间的转移方程:f(i) = 2*f(i-1)
- 初始状态:f(1) = 1
- 返回结果:f(i)
2)代码描述
class Solution {
public:
int jumpFloorII(int number) {
//写法1
/*if(number <= 1)
return number;
int fn1 = 1;//初始状态f(1)=1
int fn;//结果
for(int i = 2;i <= number;i++)
{
fn = 2*fn1;
fn1 = fn;
}
return fn;*/
//写法2
//每一项的值都是前一项的2倍,第一项的值为1,等比数列:sn = 2^n - 1
return (1<<number)/2;
}
};
3、矩形覆盖
1)问题分析
- 状态定义:i个小矩形覆盖成一个2*i的大矩形的方法f(i)
- 状态之间转移方程:f(i) = f(i-1) + f(i-2)
- 初始状态:f(1) = 1,f(2) = 2
- 返回结果:f(i)
2)代码描述
class Solution {
public:
int rectCover(int n) {
if(n <= 2)
return n;
int fn1 = 2;//f(n-1)初始状态为f(2) = 2
int fn2 = 1;//f(n-2)初始状态f(1) = 1
int fn;//返回结果
for(int i = 3;i <= n;i++)
{
fn = fn1+fn2;//状态转移方程f(n) = f(n-1) + f(n-2)
fn2 = fn1;//状态改变
fn1 = fn;
}
return fn;
}
};
4、连续子数组最大和
1)问题分析
- 状态定义: 以第i个元素结尾的最大连续子序列和f(i)
- 状态之间的转移方程:f(i) = max({f(i-1)+a[i]},a[i])
- 初始状态:f(1) = a[0]
- 返回结果:max(f1,f2,...,f(i))
2)代码描述
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) {
//写法1---保存所有以i结尾的连续子序列的最大和
/*vector<int> maxArray;
maxArray.push_back(array[0]);//初始状态
for(int i = 1;i < array.size();i++)
{
if(maxArray[i-1]+array[i] < array[i])
maxArray.push_back(array[i]);
else
maxArray.push_back(maxArray[i-1]+array[i]);
}
//选择最大的返回
int max = maxArray[0];
for(int i = 1;i < maxArray.size();i++)
{
if(maxArray[i] > max)
max = maxArray[i];
}
return max;*/
//写法2---只保存f(i-1)
int fi1 = array[0];//初始状态
int max = array[0];//返回结果
for(int i = 1;i < array.size();i++)
{
if(fi1+array[i] > array[i])
fi1 += array[i];
else
fi1 = array[i];
//更新max
if(max < fi1)
max = fi1;
}
return max;
}
};
5、分割字符串
1)问题分析
2)代码描述
class Solution {
public:
bool wordBreak(string s, unordered_set<string> &dict) {
int len = s.size();
vector<int> status(len+1,0);//保存状态
status[0] = 1;//初始状态f(0) = 1
for(int i = 1;i <= len;i++)
{
for(int j = i-1;j >= 0;j--)
{
if(status[j] && dict.find(s.substr(j,i-j)) != dict.end())
status[i] = 1;//状态之间的转换
}
}
return status[len];
}
};