前两天有点小事情,所以就断更了两天,其实还是懒。
今天来继续刷刷剑指offer吧。
今天需要用到的知识点是动态规划跟贪心算法。
首先能用动态规划来解决的问题一般有如下四个特点:
1.问题的目的是求最优解。
2.问题可以被拆分位若干个小问题,整体的最优解依赖其子问题的最优解,也可以说整体最优解可以由子问题的最优解组合得来。
3.大问题可以被拆分为若干个子问题,子问题之间还有重叠的子问题。
4.由于有共同的子问题,那么在求大问题最优解的时候可以从上往下分析,求解的时候可以从下往上求解,可以复用子问题的解,避免重复计算。
问题描述:
动态规划&&贪心解法:
public class Design {
// 动态规划
public static int findMaxAreaByCutCord(int len) {
if (len < 2) {
return 0;
}
if (len == 2) {
return 1;
}
if (len == 3) {
return 2;
}
int p[] = new int[len + 1];
p[0] = 0;
p[1] = 1;
p[2] = 2;
p[3] = 3;
for (int i = 4; i <= len; i++) {
for (int j = 1; j <= i / 2; j++) {
if (p[j] * p[i - j] > p[i]) {
p[i] = p[j] * p[i - j];
}
}
}
return p[len];
}
// 贪心
public static int findMaxAreaByCutCordTx(int len) {
if (len < 2) {
return 0;
}
if (len == 2) {
return 1;
}
if (len == 3) {
return 2;
}
int cp = len;
int res = 1;
while (cp >= 5) {
res *= 3;
cp -= 3;
}
if (cp == 0) {
cp = 1;
}
return res * cp;
}
public static void main(String[] args) {
System.out.println(findMaxAreaByCutCord(16));
System.out.println(findMaxAreaByCutCordTx(16));
}
}
动态规划解法解析:
p数组用来记录动态规划整个计算过程中的子问题的解和大问题的解。
首先初始化了四个值,分别是,数组代表了子问题的解,代表了绳子长度为3的子问题的最优值就是3,这里不用对长度为3的绳子再进行切割了,因为作为子问题,可以不切,前提是绳子的长度大于3,代表是已经被切割过为长度为3的子问题的绳子的最优解。
那么看到这里,你会不会有疑问?
为什么要初始化这三个值,为什么不再把的值也预先定义了?
前面说的,动态规划的第二个特点中说到,大问题的最优解依赖于其子问题的最优解,那么记该问题的最优解为,当时,是不是的最优解就可以用来求解呢?
其实也很简单,就列个式子:
解得
式子中的表示绳子的长度,表示对半切,从开始起,上面这个式子就满足条件了,真实情况下,还要区分奇偶,可以再区分奇偶证明一下,这里就不再证明为奇数的情况了。
所以从开始,绳子就可以进行切割了,即如果问题一开始绳子的长度大于3,比如恰巧等于4,那么可以用来表示。
如果绳子的长度大于4,那么就是一个子问题,这个时候有两种情况:
1.直接用子问题中绳子的长度(4)来表示。
2.用的子问题来求最优解。
前面的式子也证明了,当时,可以采用第2种方法来求解的。
所以其实当i>3时,的解就可以用上面第二种方式来计算了,所以只需要预定义。
对于贪心解法,其实也容易想到:
当一根绳子长度大于5的时候,绳子可以被分为2和3组成的短绳子,也就是子问题。
假设绳子长度为5,那么可以拆分为2和3,假设此时问题最优解是,
那么当绳子的长度为6的时候,假设,这时候其实不合适了,相当于,那肯定不可能呀。
所以当绳子的长度为6的时候,可能拆分的子问题的绳子的长度有一根至少为2才会更优,那么又可以拆分为2 2 3 的组合。
依此类推。
另外呢,就是书上的证明了:
当时,,,即当绳子的长度大于5的时候,我们可以把绳子拆分为2或者3的绳子段。
然后,书上又证明了:
当时,,那就尽量多来一些长度为3的绳子段吧。
另外:假设绳子长度为,那么,假设e可能为1或者2,如果为2的话就很普通,直接按书上的方法算好了。如果为1的时候,说明将拆分为子问题的乘数因子中包含4,那么,做个特殊区分即可。
ps:上面我写的贪心法跟书上的不一样,书上的更优,我的解释也是解释书上的方法。