0803—动态规划2

0.前言

动态规划的步骤是什么?
1.找到状态、选择。状态,是在变化的东西;选择,是你可以选择的东西。
2.明确dp数组的含义
3.寻找状态转移方程
4.如何选择遍历方向?遍历时的2个注意事项:(1)、所需状态必须是已经计算出来的;(2)、遍历的终点必须是存储返回结果的位置。

1. 让字符串成为回文串的最少插入次数

在这里插入图片描述
1.题目分析

  • 1.dp[][]数组,dp[i][j]表示第 i 到 j 的元素变为回文串所需最短的步骤;
  • 2.base case,i==j时,dp[i][j] = 0; i > j时,dp[i][j] = 0;
  • 3.状态转移方程,进行反向遍历,当s[i] == s [j] 时,dp[i][j] = dp[i+1][j-1];
    否则,dp[i][j] = 1 + min(dp[i+1][j] , dp[i][j-1],);
    即,选择前者在右边添加一个s[i] 和 后者在左边添加1个s[j],步数最小的那个。
  • 4.最终状态存在dp[0][len-1]中。

2.代码

class Solution {
    public int minInsertions(String s) {
        if(s == null || s.length() <= 1)
            return 0;
        
        int len = s.length();
        1.定义dp数组
        int[][] dp = new int[len][len];
        2.base case,二维数组下三角全为0
        
        3.状态转移方程 分析知,进行反向遍历,因为所需状态必须是已经计算出来的
        for (int i = len-2; i >= 0; i--) {
            for (int j = i+1; j < len; j++) {
                if (s.charAt(i) == s.charAt(j)){
                    dp[i][j] = dp[i+1][j-1];
                }else {
                    选择在右边添加一个s[i] 或者  在左边添加1个s[j]
                    dp[i][j] = 1 + Math.min(dp[i+1][j], dp[i][j-1]);
                }
            }
        }
        return dp[0][len-1];
    }
}

2. 戳气球

在这里插入图片描述

1.题目分析

  • 1.dp[][]数组,dp[i][j]表示第i个和第j个气球之间的可以得到多少硬币(不包含i、j自身);
  • 2.扩充气球数组nums变为point数组,即nums左右两边分别添加1个虚拟气球,数字为1;
  • 3.base case,当i == j时,dp[i][j] == 0,
    • 当i > j 时,dp[i][j] == 0,
    • 当i+1 == j 时,dp[i][j] == 0,
  • 4.状态转移方程,反向遍历,在i和j之间做选择,最后一个被戳得是其中哪一个?假设是第k个,那么
  • dp[i][j] = Max( dp[i][j], dp[i][k] + dp[k][j] + point[i]*point[k]*point[j] )

2.代码

class Solution {
    public int maxCoins(int[] nums) {
        
        int len = nums.length;
        为方便计算,在nums左右各自增加1个虚拟气球
        int[] point = new int[len+2];
        point[0] = 1;
        point[len+1] = 1;
        for (int i = 0; i < len; i++) {
            point[i+1] = nums[i];
        }
        
        1.定义动态数组
        int[][] dp = new int[len+2][len+2];
        
        2.base case 
        i+1 >= j  dp[i][j]==0
        
        3.状态转移方程  
        for (int i = len-1; i >= 0; i--) {
            for (int j = i+1; j < len+2; j++) {
                做选择,到底最后被戳得是 i、j 之间的哪一个?
                for (int k = i+1; k <= j-1; k++) {
                    dp[i][j] = Math.max(dp[i][j],  维持? 
                            dp[i][k] + dp[k][j] + point[i]*point[k]*point[j]);  还是更新?
                }
            }
        }
        return dp[0][len+1];
    }
}

3. 分割等和子集

在这里插入图片描述
1.题目分析

  • 1.dp数组,dp[i][j]表示,对于前i个数字,如果存在子序列sum和可以是j,那么为true,否则false;
  • 2.base case,dp[0][…] == fasle,0个数字无法组成和为其他数字(当然除0之外)
  • dp[…][0] == true,只要1个数字不选就能组成和为0.
  • 3.最终状态dp[len][sum/2];
  • 4.状态转移方程,正向遍历。在这里插入图片描述

2.代码

class Solution {
    public boolean canPartition(int[] nums) {
        int len = nums.length;
        int sum = 0;
        for (int i = 0; i < len; i++) {
            sum += nums[i];
        }
        if (sum % 2 ==1)
            return false;
        sum /= 2;
        1.动态数组
        boolean[][] dp = new boolean[len+1][sum+1];
        2.base case
        for (int i = 0; i < sum+1; i++) {
            dp[0][i] = false;
        }
        for (int i = 0; i < len+1; i++) {
            dp[i][0] = true;
        }
        3.状态转移方程
        for (int i = 1; i < len+1; i++) {
            for (int j = 1; j < sum+1; j++) {
            	1.如果背包还能装下nums[i-1]if (j - nums[i-1] >= 0){
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
                }
                2.背包不能装下nums[i-1]else
                    dp[i][j] = dp[i-1][j];
            }
        }
        return dp[len][sum];
    }
}

4. 零钱兑换

在这里插入图片描述
1.题目分析
1.选择:哪种面值的硬币
2.状态:amount-已选硬币后的剩余值
3.dp数组,dp[i]表示要凑成i元需要多少硬币数;
4.状态转移在这里插入图片描述

2.代码

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount < 0) return -1;
        if (amount == 0) return 0;

         1.dp[i] 表示凑成i,最少需要多少硬币
        int[] dp = new int[amount+1];
        Arrays.fill(dp, amount+1);
         2.base case
        dp[0] = 0;

         3.状态转移方程
        凑成的target金额,状态变化
        for (int i = 0; i <= amount; i++) {
            对硬币做选择
            for (int j = 0; j < coins.length; j++) {
                 如果当前硬币可以选
                if (i - coins[j] >= 0){
                    dp[i] = Math.min( dp[i],  dp[i-coins[j]]+1 );
                }
            }
        }
        return dp[amount]==amount+1 ? -1 : dp[amount];
    }
}

5. 零钱兑换 II

在这里插入图片描述
1.题目分析

  • 1.dp动态数组,dp[i][j] 表示前i个面值的硬币凑成数额j的数量

  • 2.base case :

  •           dp[0][..] == 0 ,0个硬币什么都凑不成(除了0元);
     		  dp[..][0] == 1 ,凑成0,只需要什么都不出,就行
    
  • 3.状态转移方程:正向遍历

  •       if(j-coins[i-1] >= 0)  // 第i个硬币可以使用时
            //前i-1个硬币组成金额j的数量  + 前i-1个硬币组成金额j-coins[i-1]的数量
            dp[i][j] = dp[i-1][j] + dp[i-1][j-coins[i-1]]
          else    // 第i个硬币不能使用时
            dp[i][j] = dp[i-1][j]
    
  • 4.目标求dp[len][amount]

2.代码

class Solution {
    public int change(int amount, int[] coins) {

        int len = coins.length;

        1.定义dp数组
        int[][] dp = new int[len+1][amount+1];
        2.base case
        for (int i = 0; i < len+1; i++) {
            dp[i][0] = 1;
        }
        3.状态转移方程
        for (int i = 1; i < len+1; i++) {
            for (int j = 1; j < amount+1; j++) {
                此时可以做选择,用或不用第i个硬币coins[i-1],两个选择加起来
                if (j-coins[i-1] >= 0) {
                    dp[i][j] = dp[i - 1][j];
                    第i个硬币coins[i-1],用几次?
                    for (int k = 1; k * coins[i-1] <= j; k++) {
                        dp[i][j] += dp[i - 1][j - k*coins[i - 1]];
                    }
                }
                此时只能不用第i个硬币coins[i-1]
                else
                    dp[i][j] = dp[i-1][j];
            }
        }
        return dp[len][amount];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值