动态规划与贪婪算法
动态规划是现在 编程面试的热门话题,如果面试题是求一个问题的最优解(最大值或者最小值),而且该问题呢广告分解成若干个子问题,并且子问题之间还有重叠的子问题,就可以考虑动态规划。
应用动态规划需要能够把大问题分解成小问题,分解的小问题也存在最优解。把小问题的最优解组合起来就能到的问题的最优解。
- 求各个子问题的最优解是第一特点
- 整体的最优解依赖于子问题的最优解是第二特点
- 把大问题分解成若干个小问题,这些小问题之间还有相互重叠的更小问题-第三个特点;
- 从上往下分析问题,从下往上求解问题,这个是动态规划求解的第四个特点;(通过数组存储子问题最优解)
贪婪算法和动态规划不一样,当应用贪婪算法解决问题的时候,每一步都可以做出一个贪婪地选择,基于这个选择,我们能确定得到最优解。
题目13:剪绳子
题目描述:
给你一根长度为n绳子,请把绳子剪成m段(m、n都是整数,n>1并且m≥1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]*k[1]*…*k[m]可能的最大乘积是多少?例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。
解题思路:
本体可以有两种不同的方法解决,先用常规的需要O(n^2)时间和O(n)空间的动态规划,接着使用O(1)时间和空间的贪心算法
动态规划:
首先定义函数f(n)为 把长度为n的绳子剪成若干段后各长度乘积的最大值,第一次剪的时候, 有n-1种可能,也就是剪出来的绳子可能是1,2.3....,n-1因此f(n) = max(f(i)*f(n-i)) 其中0<i<n;
这个是一个至上而下的递推公式,由于递归会有 很多重复子问题,从而有大量不必要计算,一个更好的办法是从下往上的顺序计算,也就是先得到f(2),f(3),再得到f(4),f(5),直到f(n),当长度为2时,可能剪成的长度都是1,因此f(2)=1,长度为3时,可能剪为1和2的两段和1,1,1的三段,但是1*1*1<1*2 因此f(3 ) = 2;
贪婪算法:
如果按照如下策略来剪绳子,则得到的各段绳子的长度乘积将最大:当n>=5时,我们尽可能多地剪长度为3的绳子;当剩下的绳子长度为4,把绳子剪成两段长度为2的绳子。
代码实现:
package swordToOffer;
import java.util.Scanner;
public class Num13_MaxValueAfterCutting {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner sc = new Scanner(System.in);
while(sc.hasNext()) {
int n = sc.nextInt();
System.out.println("input length is = "+n);
int result = MaxValueOfCutting(n);
System.out.println("max value of Dynamic Planning is = "+result);
result = MaxValueOfCutting_Greedy(n);
System.out.println("max value of Greedy is = "+result);
}
}
//动态规划求解
public static int MaxValueOfCutting(int length) {
if(length<2)
return 0;
if(length==2)
return 1;
if(length==3)
return 2;
int[] values = new int[length+1];
values[0]=0;
values[1] = 1;
values[2] = 2;
values[3] = 3;
int max = 0;
for(int i=4;i<=length;i++) {
max = 0;
for(int j=1;j<=i/2;j++) {
int value = values[j]*values[i-j]; //f(i) = f(j)*f(i-j)
if(max<value)
max = value;
values[i] = max;
}
}
max = values[length];
return max;
}
//贪婪算法
public static int MaxValueOfCutting_Greedy(int length) {
if(length<2)
return 0;
if(length==2)
return 1;
if(length==3)
return 2;
//尽可能多地剪去长度为3的绳子段
int timeOfThree = length/3;
//当绳子最后剩下的长度为4的时候,不能再剪去长度为3的绳子
//此时更好的方法是把绳子剪成长度为2的两段,因为2*2》3*1
if(length-timeOfThree*3==1)
timeOfThree-=1;
int timeOfTwo = (length-timeOfThree*3)/2;
return (int)(Math.pow(3,timeOfThree))*(int)(Math.pow(2, timeOfTwo));
}
}
题目收获:
- 对于问题的抽象建模能力,需要把一个具体的场景抽象成一个能够用动态规划或者贪婪算法解决的模型
- 动态规划是从下至上解决问题,而贪婪算法则需要扎实的数据基本功