Dynamic Programming
一,动态规划题目的特点
- 计数型
- 有多少种方式走到右下角
- 有多少种方法选出K个数使得和是Sum
- 求最大,最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
- 求存在性
- 取石子游戏,先取是否必胜
- 能不能选出K 个数使得和是Sum
二,动态规划解题步骤
- 确定状态
- 数组中的每个元素dp[i]或者dp[i][j]代表什么;数组需要开多大,是否需要加一。
- 确定最后一步和子问题(问 题一样,规模变小)
- 确定转移方程
- 确定初始条件和边界情况(用转移方程算不出来的,没有意义的,需要手动定义;边界情况:注意不要数组越界)
- 计算顺序(一般为从小到大,从上到下,从左到右)
三,习题练习
(1), 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。
代码如下(示例):
public int coinNumber(int[] A, int B){
//0~n: n+1
int[] f = new int[B+1];
int n = A.length; //number of the available coins.
//initialization
f[0]=0;
for(int i=1; i<=B; i++) {
//先假设都拼不出来
f[i]=Integer.MAX_VALUE;
//last coin A[j]
//f[j] = min{f[i-A[0]+1,~f[i-A[n]+1}
for(int j=0; j<n; j++) {
//无穷大+1会造成越界,也就是拼不出来(i-A[j])
if(i-A[j]>=0 && f[i-A[j]]!=Integer.MAX_VALUE) {
f[i]=Math.min(f[i], f[i-A[j]]+1);
}
}
}
if(f[B]==Integer.MAX_VALUE) {
return -1;
}
return f[B];
}
(2), 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(i==0 || j==0){
dp[i][j]=1;
}else{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
(3), 跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
class Solution {
public boolean canJump(int[] nums) {
boolean[] dp = new boolean[nums.length];
dp[0]=true;
for(int i=1; i<nums.length; i++){
dp[i]=false;
for(int j=0; j<i; j++){
if(dp[j] && nums[j]+j>=i){
dp[i]=true;
break;
}
}
}
return dp[nums.length-1];
}
}
(4), 跳跃游戏 II 给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
int length = nums.length;
int end = 0;
int maxPosition = 0;
int steps = 0;
for (int i = 0; i < length - 1; i++) {
maxPosition = Math.max(maxPosition, i + nums[i]);
if (i == end) {
end = maxPosition;
steps++;
}
}
return steps;