一、概念篇
1.定义
动态规划是分治思想的延伸,通俗的来说就是大事化小,小事化无的艺术。
在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。
2.特点
1.把原来的问题分解成了几个相似的子问题
2.所有的子问题都只需要解决一次
3.储存子问题的解
3.本质
动态规划的本质,是对问题状态的定义和状态转移方程的定义(状态以及状态之间的关系)
4.步骤
动态规划问题一般从以下四个角度考虑:
1.状态定义
2.状态间的转移方程定义
3.状态的初始化
4.返回结果
状态定义的要求:定义的状态一定要形成递推关系
5.适用场景
求最大值/最小值,判断可不可行,判断是不是,求方案个数
二、用例篇
1.斐波那契数列
通常我们斐波那契会采用递归的方式求解,这种方式时间复杂度为2n,当n值很大时,会产生很多的重复计算,性能就会很差,可能会导致栈溢出,计算很慢。
实际上斐波那契数列是很容易用动态规划的。其状态转移方程都是很明显的。
状态F(i):第i项的值
状态转移方程:F(i)=F(i-1)+F(i-2)
初始状态:F(0)=0,F(1)=1
返回结果:F(n)
可以创建一个数组,保存中间状态的解。
程序如下:
class Solution {
public int fib(int n) {
if(n==0){
return 0;
}
if(n==1){
return 1;
}
//创建数组保存中间状态
int[] array=new int[n+1];
//初始状态
array[0]=0;
array[1]=1;
for(int i=2;i<n+1;i++){
//状态转移方程
array[i]=array[i-1]+array[i-2];
}
return array[n];
}
}
但其实我们会发现:空间复杂度可以更优化,因为这里的数组我们只是用到前两个,最前面的元素用不到,所以我们只需要给两个临时变量动态更新,需要更新中间状态。优化程序如下:
class Solution {
public int fib(int n) {
if(n==0){
return 0;
}
if(n==1){
return 1;
}
//定义初始状态
int fb0=0;
int fb1=1;
int fb=0;
for(int i=2;i<n+1;i++){
//状态转移方程
fb=fb0+fb1;
//更新中间状态
fb0=fb1;
fb1=fb;
}
return fb;
}
}
2.单词拆分
题目:
给定一个字符串 s,和一组单词 dict,判断 s 是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是 dict 中的单词(序列可以包含一个或多个单词)
示例:
给定 s = “leetcode”,dict = [“leet”,“code”]
返回 true,因为 “leetcode” 可以被分割成 “leet”,“code”
我们可以以示例来引导:F(4):前四个字符是否可以被分割:true
在F(4)已经为true的前提下:
F(8):F(4)&&[5,8]是否可以在词典中找到:true
在没有F(4)已经为true的前提下:
F(8):要分别查找一下这几个,直到找到条件为true则break。
推导出状态转移方程为:F(i):j<i&&F(j)&&[j+1,i]是否可以在词典中找到
状态F(i):字符串前i个字符是否可以被分割
状态转移方程:F(i):j<i&&F(j)&&[j+1,i]是否可以在词典中找到
初始状态:F(0):true 辅助状态,不代表实际意义F(0)在这里的含义是空字符串是否在词典中存在,答案当然是不存在的,但这里的F(0)只是一个辅助状态,没有实际意义
当F(0)为false时,如果整个字符串在词典中存在,答案false&&true仍然为false,这是不合理的。
所以为true。返回结果:F(字符串长度):F(s.length())
代码如下:
public boolean wordBreak(String s, Set<String> dict){
// 创建一个数组用来保存状态
boolean[] canBreak = new boolean[s.length() + 1];
// 初始状态
canBreak[0] = true;
for (int i = 1; i <= s.length(); i++) {
// 状态转移方程F(i):(j < i) && F(j) && [ j+1,i ]
for (int j = 0; j < i; j++) {
//由于substring是左开右闭区间,所以这里直接写j而不是j+1
if(canBreak[j] && dict.contains(s.substring(j,i))){
canBreak[i] = true;
break;
}
}
}
return canBreak[s.length()];
}