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
个单位的木棍,棍上从 0
到 n
标记了若干位置。例如,长度为 6 的棍子可以标记如下:
给你一个整数数组 cuts
,其中 cuts[i]
表示你需要将棍子切开的位置。
你可以按顺序完成切割,也可以根据需要更改切割的顺序。
每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。
返回切棍子的 最小总成本 。
示例 1:
输入: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; } }
已解答
困难
有 n
个气球,编号为0
到 n - 1
,每个气球上都标有一个数字,这些数字存在数组 nums
中。
现在要求你戳破所有的气球。戳破第 i
个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1]
枚硬币。 这里的 i - 1
和 i + 1
代表和 i
相邻的两个气球的序号。如果 i - 1
或 i + 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
个气球,编号为0
到 n - 1
,每个气球上都标有一个数字,这些数字存在数组 nums
中。
现在要求你戳破所有的气球。戳破第 i
个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1]
枚硬币。 这里的 i - 1
和 i + 1
代表和 i
相邻的两个气球的序号。如果 i - 1
或 i + 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; }