动态规划(含习题)

一,动态规划演变过程:

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];
 }

53. 最大子序和

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;
 }

300. 最长上升子序列

	/**
	*	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]);
    }
}

P1968 美元汇率

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]));//最后需要换成美元
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值