cutRod

cutRod问题

问题:有一根铁条,不同的长度收益不同,求如何切割,可以让铁条的收益最大,铁条的收益与长度的对应关系如下表所示

length 0 1 2 3 4 5 6 7 8 9 10
profit 0 1 5 8 9 10 17 17 20 24 30

解决方案

  • 递归
    分成子问题,例如4可以分为2+2,3+1,0+4,1+3,1+1+1+1,1+1+2
  • 自顶向下的备忘录算法
    此方案仍然按照自然的递归形式编写整体的流程,但是呢,不同的是,在执行的过程中会保存每一个子问题的解(通常保存在一个数组中),当需要一个子问题的解的时候,首先会检查是否已经保存过此解。如果是,则就直接返回保存的数组中的值,如果不是,按照通常的方式解决此问题即可。
  • 自底向上法
    这种方法一般需要恰当定义子问题的规模的概念,使得任何子问题都依赖于更小的子问题的求解。因而我们可以将子问题按规模排序,并且按照由小到大排序,当求解某一个问题的时候,其子问题已经被解决,并且每一个子问题就保存计算一次。
    - 状态定义:dp[i] 表示当铁条长度为i的时候的最大收益
    - 状态转移方程:dp[i] = Math.max(dp[i],p[j] + dp[i-j]) for j = 1 to i 表示从[i-j]的长度转移而来
    - 初始状态:dp[0] = 0 表示铁条的长度为0的时候,最大收益为0
  • 自底向上并记录转移过程

为什么递归会有重复计算?(以长度为4的铁条为例子)

针对于动态规划的(也就是自底向上的方法的)子问题图

All Coding

import java.util.*;
class Solution{
	/**
	问题:有一根铁条,不同的长度收益不同,求如何切割,可以让铁条的收益最大
	铁条的收益与长度的对应关系如下表所示
	|length|0|1|2|3|4|5|6|7|8|9|10|
	|---|---|---|---|---|---|---|---|---|---|---|----|
	|profit|0|1|5|8|9|10|17|17|20|24|30|
	*/
	static int[] p = new int[]{0,1,5,8,9,10,17,17,20,24,30};
	static int[] r = new int[p.length];
	public static void main(String[] args) {
		Arrays.fill(r,Integer.MIN_VALUE);
		System.out.println("--------------------");
		System.out.println(cutRod(p,10));
		System.out.println("--------------------");
		System.out.println(memoizedCutRod(p,10,r));
		System.out.println("--------------------");
		System.out.println(bottomUpCutRod(p,10));
		System.out.println("--------------------");
		System.out.println(bottomUpCutRod2(p,7));
	}
	/**
	params:int[] p : 价格数组
	params:int n : 长度
	function:返回长度为n的钢条的最大收益
	方法:递归
	分成子问题,例如4可以分为2+2,3+1,0+4,1+3,1+1+1+1,1+1+2

	*/
	public static int cutRod(int[] p , int n){
		if(n == 0) return 0;
		int q = Integer.MIN_VALUE;
		for(int i = 1;i<=n;i++){
			q = Math.max(q,p[i] + cutRod(p,n-i));
		}
		return q;
	}
	/**
	带备忘录的自顶向下方法
	此方案仍然按照自然的递归形式编写整体的流程,但是呢,不同的是,在执行的过程中会保存每一个子问题的解(通常保存在一个数组中),当需要一个子问题的解的时候,首先会检查是否已经保存过此解。
	如果是,则就直接返回保存的数组中的值,如果不是,按照通常的方式解决此问题即可。
	params:int[] p : 价格数组
	params:int n : 长度
	params:int[] r : 备忘录record
	*/
	
	public static int memoizedCutRod(int[] p,int n,int[] r){
		if(r[n] >= 0) return r[n];
		if(n==0) return 0;
		int q = Integer.MIN_VALUE;
		for(int i = 1;i<=n;i++){
			q = Math.max(q,p[i] + memoizedCutRod(p,n-i,r));
		}	
		r[n] = q;
		return q;

	}
	/**
	自底向上方法
	这种方法一般需要恰当定义子问题的规模的概念,使得任何子问题都依赖于更小的子问题的求解。因而我们可以将子问题按规模排序,并且按照由小到大排序,当求解某一个问题的时候,其子问题已经被解决,并且每一个子问题就保存计算一次。
	状态定义:dp[i] 表示当铁条长度为i的时候的最大收益
	状态转移方程:dp[i] = Math.max(dp[i],p[j] + dp[i-j]) for j = 1 to i 表示从[i-j]的长度转移而来
	初始状态:dp[0] = 0 表示铁条的长度为0的时候,最大收益为0
	*/
	public static int bottomUpCutRod(int[] p,int n){
		int[] dp = new int[p.length];
		dp[0] = 0;
		for(int j = 1;j<=n;j++){
			int q = Integer.MIN_VALUE;
			for(int i =1;i<=j;i++){
				q=Math.max(q,p[i] + dp[j-i]);
			}
			dp[j] = q;
		}
		return dp[n];

	}
	/**
	自底向上方法并且记录转移过程
	*/
	public static int bottomUpCutRod2(int[] p,int n){
		int[] dp = new int[p.length];
		// 数组s用来记录转移过程
		int[] s = new int[p.length];
		dp[0] = 0;
		for(int j = 1;j<=n;j++){
			int q = Integer.MIN_VALUE;
			for(int i =1;i<=j;i++){
				if(q < p[i] + dp[j-i]){
					q = p[i]+dp[j-i];
					s[j] = i;
				}
			}
			dp[j] = q;
		}
		int t = n;
		while(t>0){
			System.out.print(s[t]+" ");
			t-=s[t];
		}
		System.out.println();
		return dp[n];
	}
}

posted @ 2020-12-13 13:25  BOTAK  阅读(112)  评论(0编辑  收藏  举报