算法总结

动态规划

这类问题往往需要求一个“最值”,应满足以下三个条件:

  • 最优子结构
  • 最优解
  • 重叠子问题
    思路:找出状态转移方程,有base case,根据填表来获得最终解
    经典问题

硬币问题

有一个面值为c1,c2,…ck的k枚硬币,数量不限,给一个总金额amount,求最少几枚硬币能刚好凑够?

  • 先确定**「状态」**,也就是原问题和⼦问题中变化的变量。由于硬币数量⽆
    限,所以唯⼀的状态就是⽬标⾦额 amount
  • 然后确定 dp 函数的定义:当前的⽬标⾦额是 n ,⾄少需要 dp(n) 个硬
    币凑出该⾦额。
  • 状态转移方程:
    在这里插入图片描述

戳气球

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:

你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:

输入: [3,1,5,8]
输出: 167 
解释: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
     coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

  • 状态方程:dp[start][end] = Math.max(dp[start][end],dp[start][k]+dp[k][end]+new_nums[start]*new_nums[k]*new_nums[end]);
    k表示从start到end之间的气球最后戳破的编号为气球k
  • 动态填表
    if start+1 <= end
    dp[start][end] = 0;
    从start = 3开始往上填写

当start= 3,end = 5,k = 4时,dp[3][5] = dp[3][4]+dp[4][5]+nums[3]nums[4]nums[5]=0+0+581=40
同理
start= 2,end = 4,k = 3时,dp[2][4]=nums[2]nums[4]nums[3]=581=40
start= 2,end = 5,k = 3,4时,dp[2][5]=max(dp[2][4]+nums[2]nums[4]nums[5],dp[3][5]+nums[2]nums[3]nums[5])=max(40+181,40+151)=48

1 3 1 5 8 1

012345
000320159167
10015135159
2004048
30040
400
50
class Solution {
    public int maxCoins(int[] nums) {
        if(nums.length == 0){
            return 0;
        }
        if(nums.length == 1){
            return nums[0];
        }
        if(nums.length == 2){
            return nums[0]*nums[1]+Math.max(nums[0],nums[1]);
        }
        int n = nums.length+2;
        int[][] dp = new int[n][n];   //表示编号从i到j的气球戳破的最大硬币值
        int[] new_nums = new int[n];
        new_nums[0] = new_nums[n-1] = 1;
        for(int i = 0; i < nums.length; i++){    //新数组
            new_nums[i+1] = nums[i];
        }

        for(int start = n-1;start >= 0;start--){
            for(int end = start+2;end < n;end++){
                for(int k = start+1; k < end;k++)
                    dp[start][end] = Math.max(dp[start][end],dp[start][k]+dp[k][end]+new_nums[start]*new_nums[k]*new_nums[end]);
                }
            }
        return dp[0][n-1];
    }
}

目标和

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
  1. 递归方法
class Solution {
    private int core(int[] nums,int[][] dp, int S,int index){
        if(index == 0){
            if(0 == nums[0] && S == 0){
                return 2;
            }
            else if(S == nums[0] || S+nums[0] == 0){
                return 1;
            }
            else 
                return 0;
        }
        return core(nums,dp,S+nums[index],index-1)+core(nums,dp,S-nums[index],index-1);
    }
    public int findTargetSumWays(int[] nums, int S) {
        int n = nums.length;
        int[][] dp = new int[n][2000];
       
        return this.core(nums,dp,S,n-1);
    }
}
  1. 动态规划法
    转化为找到一个正子集和一个负子集,其和为S
    sum§ - sum(N) = target
    sum§ + sum(N) + sum§ - sum(N) = target + sum§ + sum(N)
    2 * sum§ = target + sum(nums)
    转化为找到一个正子集,使得其和为S+sum的一半,类似于0 1 背包问题
class Solution {

    public int findTargetSumWays(int[] nums, int S) {
        int n = nums.length;
        int sum= 0;
        for(int i = 0;i < n;i++){
            sum += nums[i];
        }
        if(sum < S || (S+sum)%2 == 1){
            return 0;
        }
        int m = (S+sum)>>1;
        int[] dp = new int[m+1];
        dp[0] = 1;

        for(int i = 0;i < n;i++){
            for(int j = m; j >= nums[i];j--){
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[m];
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值