LeetCode每日十题---动态规划(二)

1.题目描述

在这里插入图片描述

1.1笔者分析

写动态规划简单题的时候,你会发现套路都一模一样,一个传递函数,到了中等题,就多加了几个传递函数。

class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        if(n==0)return 0;
        int[] sell=new int[n+1];
        int[] buy=new int[n+1];
        int[] cool=new int[n+1];
        sell[0]=0;
        buy[0]=-prices[0];
        cool[0]=0; 
        for(int i=1;i<n;i++){
            buy[i]=Math.max(cool[i-1]-prices[i],buy[i-1]);
            sell[i]=Math.max(buy[i-1]+prices[i],sell[i-1]);
            cool[i]=Math.max(Math.max(sell[i-1],buy[i-1]),cool[i-1]);
        }
       return sell[n-1]; 
    }
}

2.题目描述在这里插入图片描述

2.1笔者分析

记忆化回溯(也称为递归+备忘录),自顶向下。采用记忆化后的时间复杂度为O(2^n),如果不进行记忆的话,时间复杂度将是O(n!),可以理解为已压缩成了只有一个分支了。我们发现,例如[2,3]和[3,2]之后的玩家选择状态都是一样的,都是可以除了2,3之外的数字进行选择,那么就可以对选择2和3后第一个玩家能不能赢进行记忆存储。这里采用state[]数组存储每个数字是否都被选过,选过则记录为1,然后我们将state.toString()使得[2,3]和[3,2]它们的结果都是一样的"0011",作为key,存储在HashMap中,value是选了2和3之后第一个玩家是否稳赢。

    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        if (maxChoosableInteger >= desiredTotal) return true;
        if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) return false; //1,..maxChoosable数列总和都比目标和小
        int[] state = new int[maxChoosableInteger + 1];  //state[1]=1表示1被选了

        return backtraceWitMemo(state, desiredTotal, new HashMap<String, Boolean>());
    }

    private boolean backtraceWitMemo(int[] state, int desiredTotal, HashMap<String, Boolean> map) {
        String key = Arrays.toString(state); //这里比较关键,如何表示这个唯一的状态,例如[2,3]和[3,2]都是"0011",状态一样
        if (map.containsKey(key)) return map.get(key);  //如果已经记忆了这样下去的输赢结果,记忆是为了防止如[2,3],[3,2]之后的[1,4,5,..]这个选择区间被重复计算

        for (int i = 1; i < state.length; i++){
            if (state[i] == 0){ //如果这个数字i还没有被选中
                state[i] = 1;
                //如果当前选了i已经赢了或者选了i还没赢但是后面对方选择输了
                if (desiredTotal - i <= 0 || !backtraceWitMemo(state, desiredTotal - i, map)) {
                    map.put(key, true);
                    state[i] = 0; //在返回之前回溯
                    return true;
                }
                //如果不能赢也要记得回溯
                state[i] = 0;
            }
        }
        //如果都赢不了
        map.put(key, false);
        return false;
    }

3.题目描述

在这里插入图片描述

3.1笔者分析

超时了。。。。但看到评论区大佬们的解法我感觉我又可以了。
这道题类似于完全背包问题,每个物品都可以无限使用,但是要求背包必须装满,而且要求背包中的物品数目最少,归纳为数学问题就是

  • v[i]:代表每种硬币的价值
  • x[i]:代表每种硬币拿的个数,0<=x[i]<=amount/v[i]
  • 所求问题可以归纳为:
  • 在满足:amount=v1x1+v2x2+v3x3+…vnxn的条件下
  • 求:target=min{x1+x2+x3+…xn}
  • 最简单的一种思路就是把所有{xi}的组合全部拿出来然后让target最小即可,利用递归就可以解决问题,但是时间复杂度会很高,但是如果有好的剪枝策略也可以使用。
  • 另外一种方法就是常规的动态规划,利用一个amount+1长度的dp数组,记录每一个状态的最优解。

其实写了几道动态规划的题之后发现难点在于状态转移方程(这样叫只是感觉好理解)和状态对象的选取,哪些数据的存储可以减少一些不必要的计算,那么它就是状态对象,

class Solution {
    public int coinChange(int[] coins, int amount) {
       if(coins.length==0)return -1;
       int[] dp=new int[amount+1];
       Arrays.fill(dp,1,dp.length,Integer.MAX_VALUE);
       for(int i=0;i<coins.length;i++){
           for(int j=coins[i];j<=amount;j++){
               if(dp[j-coins[i]]!=Integer.MAX_VALUE){
                   dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
               }
           }
       }
       if(dp[amount]!=Integer.MAX_VALUE)
          return dp[amount];
        return -1;
    }
}

4.题目描述

在这里插入图片描述

4.1笔者分析

本来是打算和上题一样,存入每个分数的方法数,但后来发现有重复了,比如15=10+5,15=5+10,在我的算法里面就属于不同类型,但其实是重复的。但有些方法是真巧妙啊,彻底规避了这个问题。就本题而言,把硬币种类循环放外面就行了,真是牛逼啊。其实我感觉今天要写一题就做一题分析了,太舒适了。

class Solution {
    public int waysToChange(int n) {
     int[] dp=new int[n+1];
     int mod=1000000007;
     if(n<=0)return 0;
     int[] coins=new int[]{25,10,5,1};
     dp[0]=1;
     for(int coin:coins){
         for(int i=coin;i<=n;i++){
             dp[i]=(dp[i]+dp[i-coin])%mod;
         }
     }
     return dp[n];
    }
}

5.题目描述

在这里插入图片描述

5.1笔者分析

我承认有点飘了,有点简单。

class Solution {
    public int combinationSum4(int[] nums, int target) {
      int[] dp=new int[target+1];
      Arrays.sort(nums);
      dp[0]=1;
      int temp=0;
      int i,j;
      for(i=1;i<=target;i++){
          temp=0;
          for(j=0;j<nums.length;j++){
              if(i>=nums[j])temp+=dp[i-nums[j]];
              else break;
          }
          dp[i]=temp;
      }
      return dp[target];
    }
}

总结

动态规划牛逼,多状态对象,双层循环内外交换出题,每日十题三十四天,以下图为证
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路的苟狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值