动态规划三步骤
(1 )定义数组的含义,不管是一维数组dp[ i] 还或者是二维数组dp[ i] [ j] , 先声明代表的含义。
(2 )找出元素之间的关系式:dp[ n] , dp[ n- 1 ] , dp[ n- 1 ] 之间的关系。
(3 )找出初始值
案例1 :跳台阶扩展问题
题目:一只青蛙一次可以跳上1 级台阶,也可以跳上2 级……它也可以跳上n级。求该青蛙跳上一个n级的台阶( n为正整数) 总共有多少种跳法。
分析:
青蛙跳一级台阶有一种,跳二级台阶可以先跳一级,也可以直接跳两级,跳三级可以先跳一级,那么剩下两个台阶就有两个台阶的跳的方法. . . . 那么青蛙跳n级台阶可以先跳一级剩下有n- 1 级的方法,或者先跳2 级,有剩下n- 2 级的方法。
(1 )定义数组的含义:dp[ i] 就代表有i中方法跳台阶。
(2 )寻找元素之间的关系式:dp[ n] = dp[ n- 1 ] + dp[ n- 2 ] ;
(3 )找出初始值:dp[ 1 ] = 1 ; dp[ 2 ] = 2 ;
import java. util. * ;
public class Solution {
public int jumpFloorII ( int target) {
if ( target<= 2 ) return target;
int [ ] dp= new int [ target+ 1 ] ;
Arrays . fill ( dp, 1 ) ;
dp[ 0 ] = 0 ;
for ( int i= 2 ; i<= target; i++ ) {
for ( int j= i- 1 ; j>= 1 ; j-- ) {
dp[ i] += dp[ j] ;
}
}
return dp[ target] ;
}
}
案例2 :矩形覆盖(计数)
2 * 1 的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 * 1 的小矩形无重叠地覆盖一个2 * n的大矩形,从同一个方向看总共有多少种不同的方法?
分析:
当放置第n小块矩形时,一共有两种放法,要么横着,要么竖着。
当第n块小块矩形竖着放时,此时前面一共有f ( n- 1 ) 种放法;
当第n块小块矩形横着放时,n- 1 块也是横着叠放,此前一共有f ( n- 2 ) 种放法。
因此f ( n) = f ( n- 1 ) + f ( n- 2 ) ; 而f ( 0 ) = 0 , f ( 1 ) = 1 , f ( 2 ) = 2 ;
( 1 ) 定义数组的含义:dp[ i] 代表有多少种方法
( 2 ) 寻找元素之间的关系式:dp[ i] = dp[ i- 1 ] + dp[ i- 2 ] ;
( 3 ) 初始化元素:dp[ 1 ] = 1 , dp[ 2 ] = 2 ;
public class Solution {
public int rectCover ( int target) {
if ( target<= 2 ) return target;
int [ ] dp= new int [ target+ 1 ] ;
dp[ 1 ] = 1 ;
dp[ 2 ] = 2 ;
for ( int i= 3 ; i<= target; i++ ) {
dp[ i] = dp[ i- 1 ] + dp[ i- 2 ] ;
}
return dp[ target] ;
}
}
案例3 :该连续子树的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O ( n) .
分析:假设nums数组的长度为n, 下标从0 到n- 1.
f ( i) 代表以第i个数结尾的连续子数组的最大和。max{ f ( i) } , 只需要求出每个位置的f ( i) , 然后返回f数组中的最大值即可。
( 1 ) 定义数组的含义:f ( i) 代表第i个数结尾的连续子数组的最大和。
( 2 ) 找出元素之间的关系式:f ( i) = max{ f ( i- 1 ) + nums[ i] , nums[ i] } ( nums[ i] 单独为一段还是加入f ( i- 1 ) 对应的那一段)
( 3 ) 初始化:用一个变量pre来维护对于当前f ( i) 的f ( i- 1 ) 的值是多少。
class Solution {
public int maxSubArray ( int [ ] nums) {
int pre= 0 ;
int maxAns= nums[ 0 ] ;
for ( int x: nums) {
pre= Math . max ( pre+ x, x) ;
maxAns= Math . max ( maxAns, pre) ;
}
return maxAns;
}
}
案例4. 买卖股票的最佳时机
给定一个数组 prices ,其中 prices[ i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
分析:
( 1 ) 定义状态dp[ i] [ 0 ] 表示第i天交易完成后手里没有最大利润
dp[ i] [ 1 ] 表示第i天交易完成后手里有一支股票的最大利润
( 2 ) 确定关系式 确定收益最大化
dp[ i] [ 0 ] : 表示这一天交易完成后手里没有股票,可能是前一天已经没有了股票,即dp[ i- 1 ] [ 0 ] .
或者是前一天结束的时候手里持有一支股票dp[ i- 1 ] [ 1 ] , 这个时候将其卖出,并获得prices[ i] 的收益,因此收益最大化。
dp[ i] [ 0 ] = max{ dp[ i- 1 ] [ 0 ] , dp[ i- 1 ] [ 1 ] + prices[ i] }
dp[ i] [ 1 ] : 表示可能前一天已经持有一支股票,即dp[ i- 1 ] [ 1 ] , 或者前一天结束时还没有股票,dp[ i- 1 ] [ 0 ] , 这时要将其买入,并减少prices[ i] 的收益
dp[ i] [ 1 ] = max{ dp[ i- 1 ] [ 1 ] , dp[ i- 1 ] [ 0 ] - prices[ i] }
( 3 ) 初始状态:第0 天交易结束时,dp[ 0 ] [ 0 ] = 0 , dp[ 0 ] [ 1 ] = - prices[ 0 ] . ( 第0 天交易就买入)
持有股票的收益一定低于不持有股票的收益,dp[ n- 1 ] [ 0 ] 的收益必然是大于dp[ n- 1 ] [ 1 ] 的
class Solution {
public int maxProfit ( int [ ] prices) {
int n= prices. length;
int [ ] [ ] dp= new int [ n] [ 2 ] ;
dp[ 0 ] [ 0 ] = 0 ;
dp[ 0 ] [ 1 ] = - prices[ 0 ] ;
for ( int i= 1 ; i< n; ++ ) {
dp[ i] [ 0 ] = Math . max ( dp[ i- 1 ] [ 0 ] , dp[ i- 1 ] [ 1 ] + prices[ i] ) ;
dp[ i] [ 1 ] = Math . max ( dp[ i- 1 ] [ 1 ] , dp[ i- 1 ] [ 0 ] - prices[ i] ) ;
}
return dp[ n- 1 ] [ 0 ] ;
}
}
案例5. 判断子序列
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace" 是"abcde" 的一个子序列,而"aec" 不是)。
分析:动态规划的思想是希望连续的,就是说上一个状态和下一个状态之间有关系且连续。
( 1 ) 定义数组的含义:
dp[ i] [ j] : 表示字符串t的前i个元素和字符串s的前j个元素中公共子序列的长度
( 2 ) 寻找元素之间的关系:若当前字符相同,找到了一个公共元素,此时要在原来已经找到当前最长公共子序列的基础上加1 ,即f ( i- 1 ) ( j- 1 ) 的基础上加1 ,dp[ i] [ j] = dp[ i- 1 ] [ j- 1 ] + 1.
若当前字符不同,此时相当于字符串t要将当前元素删除,t如果把当前元素t[ i- 1 ] 删除,此时公共子序列的长度就是原来已经求得的公共子序列的长度,所以dp[ i] [ j] 的值就是看s[ j- 1 ] 与t[ i- 2 ] 的比较结果了,此时状态转移方程:dp[ i] [ j] = dp[ i- 1 ] [ j]
class Solution {
public boolean isSubsequence ( String s, String t) {
int n= s. length ( ) ;
int m= t. length ( ) ;
if ( n> m) return false ;
int [ ] [ ] dp= new int [ m+ 1 ] [ n+ 1 ] ;
for ( int i= 1 ; i<= m; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
if ( t. charAt ( i- 1 ) == s. charAt ( j- 1 ) ) {
dp[ i] [ j] = dp[ i- 1 ] [ j- 1 ] + 1 ;
} else {
dp[ i] [ j] = dp[ i- 1 ] [ j] ;
}
}
}
if ( dp[ m] [ n] == n) return true ;
else return false ;
}
}