什么是动态规划?
动态规划是分治思想的延伸,通俗一点来说就是大事化小,小事化了。
在将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。
动态规划具备以下三个特点:
- 把原来的问题分解成了几个相似的子问题。
- 所有的子问题都只需要解决一次。
- 储存子问题的解
步骤:
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列。动态规划的设计都有着一定的模式,一般要经历以下几个步骤:
初始状态→决策1→决策2→…→决策n→结束状态
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
在面试中我们也经常会见到一些有关动态规划的问题:
1:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:
状态F(i):跳上i级台阶的方法数
状态递推:
n级台阶,第一步有n种跳法:跳1级、跳2级、…一次到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
跳3级,剩下n-3级,则剩下跳法是f(n-3)
f(n) = f(n-1)+f(n-2)+…+f(0)
f(n-1) = f(n-2)+…+f(0)
f(n) = 2f(n-1)
初始状态:f(1)=1
f(1) = 1
f(2) = 2f(1) = 2
f(3) = 2f(2) = 4
f(4) = 2f(3) = 8
…
f(n)=2^(n-1)
返回结果:f(N)
代码实现:
public class Solution {
public int JumpFloorII(int target) {
if(target<=0){
return 0;
}
int count=1;
count=count<<(target-1);
return count;
}
}
- 给定一个字符串和一个词典dict,确定s是否可以根据词典中的词分成一个或多个单词。
比如,给定
s = “leetcode”
dict = [“leet”, “code”]
返回true,因为"leetcode"可以被分成"leet code"
分析:
状态:F(i): 前i个字符能否根据词典中的词被成功分词
F(i) : 1~j (j+1)~i
状态递推:
F(1):“l” false
F(2):“le”,“l”–>F(1)&&“e” false
F(3):“lee” “l”–>F(1) &&"ee,“le”–>F(2)&&“e” false
F(4):“leet” true
F(5):“leetc”,“l” “eetc”,“le” “etc”,“lee” “tc”,“leet”&&“c” false
…
F(8):“leetcode”,F(1),F(2),F(3),“leet”—>F(4)&&“code” true
在j小于i中,只要能找到一个F(j)为true,并且从j+1到i之间的字符能在词典中找到,则F(i)为true
初始值:
对于初始值无法确定的,可以引入一个不代表实际意义的空状态,作为状态的起始空状态的值需要保证状态递推可以正确且顺利的进行.
F(0) = true
返回结果:F(n)
代码实现:
import java.util.Set;
public class Solution {
public boolean wordBreak(String s, Set<String> dict) {
if(s.length()==0){
return false;
}
if(dict.isEmpty()){
return false;
}
boolean[] str=new boolean[s.length()+1];
str[0]=true;
for(int i=1;i<=s.length();i++){
for(int j=0;j<i;j++){
if(str[j]&&dict.contains(s.substring(j,i))){
str[i]=true;
break;
}
}
}
return str[s.length()];
}
}
3.给定一个三角矩阵,找出自顶向下的最短路径和,每一步可以移动到下一行的相邻数字。
比如给定下面一个三角矩阵,自顶向下的最短路径和为11。
分析:
如下图所示:
方法一:从(0,0)到(i,i)
每一步可以移动到相同列的下一行以及后一个
即:(i,j)—>(i+1,j) (i+1,j+1)
(i-1,j),(i-1,j-1)----->(i,j)
状态:
F(i,j): 从(0,0)到(i,j)的最短路径和
状态递推:
F(i,j) = min( F(i-1, j-1), F(i-1, j)) + triangle[i][j]
边界:
F(i,0):F(i-1,0)+ triangle[i][0]
F(i,i):F(i-1,i-1]++ triangle[i][i]
初始化:
F(0,0)=+ triangle[0][0]
返回结果:
min(F(row-1, i))
方法二:
反向思维
状态:
F(i,j): 从(i,j)到最后一行的最短路径和
从(n,n),(n,n-1),…(1,0),(1,1),(0,0)到最后一行的最短路径和
状态递推:
F(i,j) = min( F(i+1, j), F(i+1, j+1)) + triangle[i][j]
初始值:
F(row-1,0) = triangle[row-1][0], F(row-1,1) = triangle[row-1][1],…, F(row-1,row-1) = triangle[row-1][row-1]
返回结果:
F(0, 0)
注:此方法不需要考虑边界,也不需要最后寻找最小值,直接返回F(0,0)即可
代码实现:
import java.util.*;
public class Solution {
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
// 从倒数第二行开始
for(int i=triangle.size()-2;i>=0;i--){
for(int j=0;j<triangle.get(i+1).size()-1;j++){
int min=Math.min(triangle.get(i+1).get(j),triangle.get(i+1).get(j+1));
triangle.get(i).set(j,triangle.get(i).get(j)+min);
}
}
return triangle.get(0).get(0);
}
}
- 在一个m*n的网格的左上角有一个机器人,机器人在任何时候只能向下或者向右移动,机器人试图到达网格的右下角,有多少可能的路径。
分析:
状态: F(i,j): 从(0,0)到达F(i,j)的路径数
状态递推:
F(i,j)=F(i-1,j)+F(i,j-1)
初始化:
F(0,i)=1;
F(i,0)=1
返回结果:
F(m-1,n-1)
代码实现:
public class Solution {
public int uniquePaths(int m, int n) {
int[][] array=new int[m+1][n+1];
for(int i=0;i<=m;i++){
array[i][1]=1;
}
for(int j=0;j<=n;j++){
array[1][j]=1;
}
for(int i=2;i<=m;i++){
for(int j=2;j<=n;j++){
array[i][j]=array[i-1][j]+array[i][j-1];
}
}
return array[m][n];
}
}
5.机器人还是要从网格左上角到达右下角,但是网格中添加了障碍物,障碍物用1表示
即:若为1就阻碍了,不能走
状态:
F(i,j): 从(0,0)到达F(i,j)的路径数
状态递推:
(i,j)==0: F(i-1,j)+F(i,j-1)
(i,j)==1: 0
第0行和第0列
F(0,i) = {1} OR {0, if obstacleGrid(0,j) = 1, j <= i}
F(i,0) = {1} OR {0, if obstacleGrid(j,0) = 1, j <= i}
返回结果
F(m-1,n-1)
代码实现:
public class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
if(obstacleGrid.length==0){
return 0;
}
int row=obstacleGrid.length;
int col=obstacleGrid[0].length;
int[][] array=new int[row+1][col+1];
for(int i=0;i<row;i++){
if(obstacleGrid[i][0]==1){
break;
}else{
array[i][0]=1;
}
}
for(int i=0;i<col;i++){
if(obstacleGrid[0][i]==1){
break;
}else{
array[0][i]=1;
}
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
if(obstacleGrid[i][j]==0)
array[i][j]=array[i-1][j]+array[i][j-1];
}
}
return array[row-1][col-1];
}
}
- 给定一个m*n的网格,网格用非负数填充,找到一条从左上角到右下角的最短路径。每次只能向下或者向右移动
状态:
F(i,j): 从(0,0)到达F(i,j)的最短路径
状态递推:
F(i,j) = min{F(i-1,j) , F(i,j-1)} + array(i,j)
行和列:
array[0][j]=array[0][j-1]+array[0][j]
array[i][0]=array[i-1][0]+array[i][0]
初始:
F(0,0)=(0,0)
返回结果:
F(m-1,n-1)
代码实现:
public class Solution {
public int minPathSum(int[][] grid) {
if(grid==null||grid[0]==null){
return 0;
}
int row=grid.length;
int col=grid[0].length;
int[][] array=new int[row+1][col+1];
array[0][0]=grid[0][0]; //初始化
for(int i=1;i<row;i++){
array[i][0]=grid[i][0]+array[i-1][0];
}
for(int i=1;i<col;i++){
array[0][i]=grid[0][i]+array[0][i-1];
}
for(int i=1;i<row;i++){
for(int j=1;j<col;j++){
array[i][j]=grid[i][j]+Math.min(array[i-1][j],array[i][j-1]);
}}
return array[row-1][col-1];
}
}
7.有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?
分析:
如下图所示:
则状态:
F(i, j): 前i个物品放入大小为j的背包中所获得的最大价值。
状态递推:
对于第i个商品,有两种选择,放和不放
(1)如果装不下(即不放):
此时的价值与前i-1个的价值是一样的 ,即F(i,j) = F(i-1,j)
(2)放a[i]
F(i,j)=F(i-1,j-a[i]))+v[i]
F(i-1, j - a[i]) + v[i]:表示把第i个物品放入背包中,价值增加v[i],但是需要腾出j - a[i]的大小放第i个商品
初始:
F(0,j) = F(i,0) = 0
没有装物品时的价值都为0
返回结果:
F(n,m)
如上图所示,对商品个数及背包容量进行递推,列出一个表格。若物品为3,即C,当背包为3时,C的大小为4,大于背包的容量,不能放入,因此当前背包中存放的依然是商品A,当背包容量为5时,可以将A和C商品都放入,此时的价值为2.其他的过程和这一样。
代码实现:
public int backPackII(int m, int[] A, int[] V) {
// write your code here
if(A==null||V==null||m<1){
return 0;
}
//多加一行一列,用于设置初始条件
int N=A.length+1;
int M=m+1;
int array[][]=new int[N][M];
for(int i=0;i<N;i++)
{ array[i][0]=0;}
for(int j=0;j<M;j++){
array[0][j]=0;
}
for(int i=1;i<N;i++ ){
for(int j=1;j<M;j++){
//第i个商品在A中对应的索引为i-1: i从1开始
//如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
if(A[i-1]>j){
array[i][j]=array[i-1][j];
}
else{
int newValue=array[i-1][j-A[i-1]]+V[i-1];
array[i][j]=Math.max(newValue,array[i-1][j]);
}
}
}
return array[N-1][m];
}
动态规划是很常见的一类问题