24.区间dp-上

24.区间dp-上

前置知识: 二维动态规划 & 空间压缩技巧

递归入手二维动态规划,整个套路请务必掌握!

【必备】课程的动态规划大专题从讲解18开始,建议从头开始学习会比较系统

区间dp:大范围的问题拆分成若干小范围的问题来求解

可能性展开的常见方式: 1)基于两侧端点讨论的可能性展开 2)基于范围上划分点的可能性展开

区间dp专题分为上、下两期,一共12个题 本节课 区间dp-上 会安排5个题,熟悉区间dp的可能性展开

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

已解答

困难

提示

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数

「回文串」是正读和反读都相同的字符串。

示例 1:

输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。
public static int minInsertions(String s) {
        int [][] dp = new int[s.length()][s.length()];
        char [] chars = s.toCharArray();
        for(int i = 0;i<chars.length-1;i++){
            dp[i][i] = 0;
            dp[i][i+1] = chars[i]==chars[i+1] ? 0:1;
        }
        dp[chars.length-1][chars.length-1] = 0;
        for(int i = chars.length-3;i>=0;i--){
            for(int j = i+2;j<chars.length;j++){
                if(chars[i]==chars[j]){
                    dp[i][j] =dp[i+1][j-1];
                }else{
                    dp[i][j] = Math.min(1+dp[i+1][j],1+dp[i][j-1]);
                }
            }
        }
        return dp[0][chars.length-1];
        //return process(chars,0,chars.length-1); //递归版本
    }
//    public int process(char [] chars,int l,int r){
//        if(dp[l][r]!=-1){
//            return dp[l][r];
//        }
//        if(l+1==r){
//            dp[l][r] = chars[l]==chars[r] ? 0:1;
//            return dp[l][r];
//        }
//        //剩下的元素不止两个
//        if(chars[l]==chars[r]){
//            dp[l][r] = process(chars,l+1,r-1);
//            return  dp[l][r] ;
//        }else{
//            int p1 = 1 + process(chars,l+1,r);
//            int p2 = 1 + process(chars,l,r-1);
//            dp[l][r] = Math.min(p1,p2);
//            return dp[l][r];
//        }
//    }

486. 预测赢家

已解答

中等

给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。

玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手。开始时,两个玩家的初始分值都是 0 。每一回合,玩家从数组的任意一端取一个数字(即,nums[0]nums[nums.length - 1]),取到的数字将会从数组中移除(数组长度减 1 )。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。

如果玩家 1 能成为赢家,返回 true 。如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true 。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入:nums = [1,5,2]
输出:false
解释:一开始,玩家 1 可以从 1 和 2 中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。 
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 false 。
static int [][] dp = new int[20][20];
static int count;
public boolean predictTheWinner(int[] nums) {
    int sum = 0;
    for (int i = 0;i<nums.length;i++){
        Arrays.fill(dp[i],-1);
        sum+=nums[i];
        dp[i][i] = nums[i];
    }
    int head = 0;
    int tail = nums.length-1;
    count = process(nums,head,tail);
    return count>=sum-count;
}
​
private int process(int[] nums, int head, int tail) {
    if (dp[head][tail]!=-1){
        return dp[head][tail];
    }
    if (head==tail){
        dp[head][tail] = nums[tail];
        return dp[head][tail];
    }else if (head+1==tail){
        return Math.max(nums[head],nums[tail]);
    }
    // 此时 1 可以选择 head 和 tail
    int p1 = nums[head]+Math.min(process(nums,head+1,tail-1),process(nums,head+2,tail));
    int p2 = nums[tail]+Math.min(process(nums,head+1,tail-1),process(nums,head,tail-2));
    dp[head][tail] = Math.max(p1,p2);
    return dp[head][tail];
}

有兴趣的话可以自己修改为dp位置依赖版本

1547. 切棍子的最小成本

已解答

困难

相关标签

相关企业

提示

有一根长度为 n 个单位的木棍,棍上从 0n 标记了若干位置。例如,长度为 6 的棍子可以标记如下:

img

给你一个整数数组 cuts ,其中 cuts[i] 表示你需要将棍子切开的位置。

你可以按顺序完成切割,也可以根据需要更改切割的顺序。

每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。

返回切棍子的 最小总成本

示例 1:

img

输入:n = 7, cuts = [1,3,4,5]
输出:16
解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示:
​
第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。
而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。
static int [][] dp = new int[102][102];
public static int minCost(int n, int[] cuts) {
    Arrays.sort(cuts);
    int ans = Integer.MAX_VALUE;
    int length = cuts.length;
    int [] arr = new int[length+2];
    arr[length+1] = n;
    for (int i = 0;i<cuts.length;i++){
        arr[i+1] = cuts[i];
    }
    int l = 0;
    int r = length+1;
    for (int i = 0;i<r;i++){
        Arrays.fill(dp[i],-1);
    }
    
    for (int i = 1;i<cuts.length+1;i++){
        ans = process(arr,l,r);
    }
    return ans;
}
​
​
public static int process(int[] arr,int l,int r){
    if (dp[l][r]!=-1){
        return dp[l][r];
    }
    if (r-1==l){
        return 0;
    }else {
        int ans = Integer.MAX_VALUE;
        for (int i = l+1;i<r;i++){
            ans = Math.min(process(arr,l,i)+process(arr,i,r)+arr[r]-arr[l],ans);
        }
        dp[l][r] = ans;
        return ans;
    }
}

312. 戳气球

已解答

困难

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

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

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

示例 1:

输入:nums = [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
static int [][]dp = new int[302][302];
static int [] arr = new int[302];
public static int maxCoins(int[] nums) {
    int n = nums.length;
    for (int i = 0;i<n+2;i++){
        Arrays.fill(dp[i],-1);
    }
    //假设首尾存在气球,但是不能戳爆
    arr[0] = 1;
    arr[n+1] = 1;
    for (int i = 0;i<n;i++){
        arr[1+i] = nums[i];
    }
    return process(arr,1,nums.length);
}
​
//求l...r上所能获得硬币的最大数量并且 l-1和r+1是存在气球的。
public static int process(int[] nums,int l,int r){
    if (dp[l][r]!=-1){
        return dp[l][r];
    }
    if(l==r){
        dp [l][r] = nums[l-1]*nums[r+1]*nums[r];
        return dp [l][r];
    }else {
        int max = 0;
        for (int i = l+1;i<r;i++){
            // 假设i点的气球是l...r上最后爆的
            max = Math.max(max,process(nums,l,i-1)+process(nums,i+1,r)+nums[l-1]*nums[i]*nums[r+1]);
        }
        max = Math.max(max,process(nums,l,r-1)+nums[r]*nums[r+1]*nums[l-1]);
        max = Math.max(max,process(nums,l+1,r)+nums[l]*nums[l-1]*nums[r+1]);
        dp [l][r] = max;
        return max;
    }
}

312. 戳气球

已解答

困难

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

现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。

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

示例 1:

输入:nums = [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
static int [][]dp = new int[302][302];
static int [] arr = new int[302];
public static int maxCoins(int[] nums) {
    int n = nums.length;
    //假设首尾存在气球,但是不能戳爆
    arr[0] = 1;
    arr[n+1] = 1;
    for (int i = 0;i<n;i++){
        arr[1+i] = nums[i];
    }
    //严格位置依赖版本
    for (int i = 1;i < n+1;i++){
        Arrays.fill(dp[i],-1);
        dp[i][i] = arr[i-1]*arr[i+1]*arr[i];
    }
    for (int i = n+1;i>=1;i--){
        for (int j = i+1;j<n+1;j++){
            for (int k = i+1;k<j;k++){
                // 假设i点的气球是l...r上最后爆的
                dp[i][j] = Math.max(dp[i][j],dp[i][k-1]+dp[k+1][j]+arr[i-1]*arr[k]*arr[j+1]);
            }
            dp[i][j] = Math.max(dp[i][j] ,dp[i][j-1]+arr[j]*arr[j+1]*arr[i-1]);
            dp[i][j]  = Math.max(dp[i][j] ,dp[i+1][j]+arr[i]*arr[i-1]*arr[j+1]);
        }
    }
    return dp[1][n];
}

1039. 多边形三角剖分的最低得分

已解答

中等

提示

你有一个凸的 n 边形,其每个顶点都有一个整数值。给定一个整数数组 values ,其中 values[i] 是第 i 个顶点的值(即 顺时针顺序 )。

假设将多边形 剖分n - 2 个三角形。对于每个三角形,该三角形的值是顶点标记的乘积,三角剖分的分数是进行三角剖分后所有 n - 2 个三角形的值之和。

返回 多边形进行三角剖分后可以得到的最低分

示例 1:

输入:values = [1,2,3]
输出:6
解释:多边形已经三角化,唯一三角形的分数为 6。

示例 2:

输入:values = [3,7,4,5]
输出:144
解释:有两种三角剖分,可能得分分别为:3*7*5 + 4*5*7 = 245,或 3*4*5 + 3*4*7 = 144。最低分数为 144。

示例 3:

输入:values = [1,3,1,4,1,5]
输出:13
解释:最低分数三角剖分的得分情况为 1*1*3 + 1*1*4 + 1*1*5 + 1*1*1 = 13。
static int [][]dp = new int[51][51];
public static int minScoreTriangulation(int[] values) {
    //构成的所有的三角形中,必然存在values[0]*values[n]*某一条边构成的三角形
    for (int i = 0;i<values.length;i++){
        Arrays.fill(dp[i],-1);
        dp[i][i+1] = 0;
        dp[i][i] = 0;
    }
    return process(values,0,values.length-1);
}
public static int process(int[] values,int l,int r){
    if (dp[l][r]!=-1){
        return dp[l][r];
    }
    if (l+1>=r){
        //此时不能构成三角形
        return 0;
    } else if (l+2==r){
        //刚好三个点能够构成三角形
        return dp[l][r]=values[l]*values[l+1]*values[r];
    }
    int ans = Integer.MAX_VALUE;
    for (int i = l+1;i<r;i++){
        //以l和r连成一条线为底边,和其他所有点组成的三角形取最小值
        ans = Math.min(ans,process(values,l,i)+process(values,i,r)+values[i]*values[l]*values[r]);
    }
    return dp[l][r]=ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值