一,动态规划演变过程:
1,暴力递归(自下向上)
2,记忆化搜索(自下向上)
3,递推(自底向上)
二,动态规划特征:
① 将复杂的原问题拆解成若干个简单的子问题
② 每个子问题仅仅解决1次,并保存它们的解
③ 最后推导出原问题的解
◼ 可以用动态规划来解决的问题,通常具备2个特点
最优子结构(最优化原理):通过求解子问题的最优解,可以获得原问题的最优解
无后效性
✓ 某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关)
✓ 在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的
三,动态规划的常规步骤:
① 定义状态(状态是原问题、子问题的解)
比如定义 dp(i) 的含义
② 设置初始状态(边界)
比如设置 dp(0) 的值
③ 确定状态转移方程
比如确定 dp(i) 和 dp(i – 1) 的关系
四,相关题目练习:
leetcode:
322. 零钱兑换
public int coinChange(int[] coins, int amount) {
if (amount == 0) return 0;
if (coins == null || coins.length == 0) return -1;
int[] dp = new int[amount + 1];//dp[i]:凑到i元需要的最少硬币个数
for (int i = 1; i <= amount; i++) {
int min = Integer.MAX_VALUE;
for (int coin : coins) {
if (i < coin) continue;
if (dp[i - coin] < 0 || min <= dp[i - coin]) continue;
min = dp[i - coin];
}
if (min == Integer.MAX_VALUE)
dp[i] = -1;//无法凑出目标零钱
else
dp[i] = min + 1;
}
return dp[amount];
}
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int dp =nums[0];
int max = dp;
for (int i = 1; i < nums.length; i++) {
int now = nums[i];
if (dp <= 0) {
dp = now;
} else {
dp += now;
}
max = Math.max(dp, max);
}
return max;
}
/**
* n^2解法
*/
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
//确定状态
int dp[] = new int[nums.length];//dp[i]:以i结尾的最长上升子序列
int Max = dp[0] = 1;//初始值
for (int i = 1; i < dp.length; i++) {
int max = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {//严格递增
max = Math.max(max, dp[j] + 1);
}
}
dp[i] = max;
Max = Math.max(max, Max);
}
return Max;
}
/**
* 二分搜索O(nlogn)+贪心思想
*/
public int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) return 0;
//确定状态
int top[] = new int[nums.length];//牌堆
int len = 0;//牌堆长度
for (int num : nums) {
int begin = 0, end = len;
while (begin < end) {//找到第一个大于等于num的牌堆
int mid = (begin + end) >> 1;
if (num <= top[mid]) {
end = mid;
} else {
begin = mid + 1;
}
}
//覆盖牌顶
top[begin] = num;
if (begin == len) len++;
}
return len;
}
洛谷:
P1002 过河卒
import java.util.Scanner;
/**
* @author Wchert
*/
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a = in.nextInt();
int b = in.nextInt();
int c = in.nextInt();
int d = in.nextInt();
long dp[][] = new long[a + 1][b + 1];//走到该位置共有多少种走法
dp[0][0] = 1;
//马的位置
dp[c][d] = -1;
//设置马的攻击范围
//右上
if (c - 2 >= 0 && d + 1 <= b)
dp[c - 2][d + 1] = -1;
if (c - 1 >= 0 && d + 2 <= b)
dp[c - 1][d + 2] = -1;
//右下
if (c + 2 <= a && d + 1 <= b)
dp[c + 2][d + 1] = -1;
if (c + 1 <= a && d + 2 <= b)
dp[c + 1][d + 2] = -1;
//左上
if (c - 2 >= 0 && d - 1 >= 0)
dp[c - 2][d - 1] = -1;
if (c - 1 >= 0 && d - 2 >= 0)
dp[c - 1][d - 2] = -1;
//左下
if (c + 2 <= a && d - 1 >= 0)
dp[c + 2][d - 1] = -1;
if (c + 1 <= a && d - 2 >= 0)
dp[c + 1][d - 2] = -1;
//第一横行
for (int i = 1; i <= b; i++) {
if (dp[0][i] == -1) continue;
dp[0][i] = dp[0][i - 1];
}
//第一列
for (int i = 1; i <= a; i++) {
if (dp[i][0] == -1) continue;
dp[i][0] = dp[i - 1][0];
}
for (int i = 1; i <= a; i++) {
for (int j = 1; j <= b; j++) {
if (dp[i][j] == -1) continue;//无法走
if (dp[i - 1][j] != -1) {
dp[i][j] += dp[i - 1][j];
}
if (dp[i][j - 1] != -1) {
dp[i][j] += dp[i][j - 1];
}
}
}
System.out.println(dp[a][b]);
}
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
double[] nums = new double[n + 1];
for (int i = 1; i <= n; i++) {
nums[i] = in.nextDouble() / 100.0;
}
double[][] dp = new double[2][2];//滚动数组,减低空间复杂度
//dp[i][0]:第i填换成美元的最大收益,dp[i][1]:第i填换成马克的最大收益
//初始化
dp[1][0] = 100;
dp[1][1] = nums[1] * 100;
for (int i = 2; i < nums.length; i++) {//每天有两种选择,不换和换
int now = i & 1, pre = i - 1 & 1;
dp[now][0] = Math.max(dp[pre][0], dp[pre][1] / nums[i]);//马克换美元
dp[now][1] = Math.max(dp[pre][1], dp[pre][0] * nums[i]);//美元换马克
}
System.out.printf("%.2f", Math.max(dp[1][0], dp[1][1] / nums[n]));//最后需要换成美元
}
}