题目描述:
将一条长度为x的线段切成若干段,切割点已给出,每次切割的成本为切割后产生的两段线段长度之和,求最小的切割成本。
示例
输入:
20,[2,5,10,18]
返回值:
45
说明:
线段长为20,切割点为[2,5,10,18]。
第一种方案:
1.先切开第一个点,成本为2+18=20
2.切开第二个点,成本为3+15=18
3.切开第三个点,成本为5+10=15
4.切开第四个点,成本为8+2=10
总成本为:20 + 18 + 15 + 10 = 63;
第二种方案:
1.先切开第一个点,成本为5+15=20
2.切开第二个点,成本为2+3=5
3.切开第三个点,成本为5+10=15
4.切开第四个点,成本为8+2=10
总成本为:20 + 5 + 15 + 10 = 50;
方法一:记忆化搜索
将一段绳子按照切割点数组中的切割点进行切割,每次切割后的子绳子的切割方式与第一次切割的方式是一样的,换句话说采用分治的思想,将一个核心的主问题分解为几个子问题,由子问题的解递推出主问题解的表达式称为状态转移方程,
先对切割点数组进行递增排序,计算每一个子区间的长度时我们需要用到切割点的绳子始端和末端,所以在切割点数组中加入0和绳子长度值
那么,怎么划分子问题呢?如果先从k点切割,则和的解都依赖于切割点k和两个边界点,由于分治法中子问题是互相独立的,所以划分问题时,k先不切割,或者说k是最后一个切割的点,求出到k的解和k到的解,并且在两个子问题解决后,切割序列还剩下 k ,那么我们用两个子问题的解与切割 k 和两个边界的成本最大值即可求出原问题的解。那么 函数的定义则为,仅切割 i 与 j 之间的点我们能得到的最小成本。如此划分,状态转移方程为:
因为 k 是介于 i 与 j 之间的,那么当 i 与 j 相邻时我们的问题将不能再继续划分。此时按照我们对问题的定义,回归条件就是 i 与 j 之间没有切割点,我们不用切割,切割成本就是0,有了转移方程和回归条件,问题就可以解决了
这里使用记忆数组记录计算过的值,避免重复计算
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param monsterLength int整型 线段长度 * @param monsterPoint int整型一维数组 切割点位置 * @return int整型 */ private int[][]memory; public int attackMonster (int monsterLength, int[] monsterPoint) { // write code here int n=monsterPoint.length; int[]nums=new int[n+2]; Arrays.sort(monsterPoint); //将monsterPoint数组复制到nums数组的1~n-1位置 System.arraycopy(monsterPoint,0,nums,1,n); //将0和绳子的长度值添加到nums数组的首端和尾端 nums[0]=0; nums[n+1]=monsterLength; memory=new int[n+2][n+2]; int res=Max(nums,n+2,0,n+1,memory); return res; } int Max(int[]nums,int len,int start,int end,int[][]memory){ //base case if(start==end-1)return 0; //已经被计算过的直接返回 if(memory[start][end]!=0)return memory[start][end]; int min=Integer.MAX_VALUE; //切割点从start+1到end-1 for(int k=start+1;k<end;k++){ min=Math.min(min,Max(nums,len,start,k,memory)+Max(nums,len,k,end,memory)+nums[k]-nums[start]+nums[end]-nums[k]); } memory[start][end]=min; return min; } }
复杂度:
时间复杂度:递归函数每次操作的平均复杂度为,区间数为 ,最终复杂度为
空间复杂度:取决于记忆数组的大小,所以是 (n为切割点数组的大小)
方法二:动态规划
方法一为自顶向下的记忆化搜索,可以转化为自底向上的动态规划,将前者的递归换成后者的递推 表示之间的最小切割成本,k作为切割点只能取
边界条件是时,也就是没有切割点时最小切割成本为0
状态转移方程:
状态是由和递推来的,;那么要得到,从表中值从递减到0,值由递增到推演即可
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param monsterLength int整型 线段长度 * @param monsterPoint int整型一维数组 切割点位置 * @return int整型 */ public int attackMonster (int monsterLength, int[] monsterPoint) { // write code here int n=monsterPoint.length; Arrays.sort(monsterPoint); //dp[i][j]表示monsterPoint(i...j)之间的最小切割成本,k作为切割点只能取[i+1,j-1] int[][]dp=new int[n+2][n+2]; int[]nums=new int[n+2]; System.arraycopy(monsterPoint,0,nums,1,n); //添加0和monsterLength切割点 nums[0]=0; nums[n+1]=monsterLength; int len=nums.length; //dp:i为start,j为end,k为切割点 for(int i=len-2;i>=0;i--){ for(int j=i+2;j<len;j++){ //如果i,j相邻,切割成本为0 if(i==j-1)dp[i][j]=0; else{ int min=Integer.MAX_VALUE; for(int k=i+1;k<j;k++){ min=Math.min(min,dp[i][k]+dp[k][j]+nums[k]-nums[i]+nums[j]-nums[k]); }dp[i][j]=min; } } }return dp[0][len-1]; } }
复杂度:
时间复杂度:三层循环,时间复杂度为
空间复杂度:数组的大小为,空间复杂度为