面试题14:剪绳子
解题思路:
书上说的解题办法是动态规划或者贪婪算法。
1)动态规划
动态规划就是把大问题划分为一个一个小的子问题,然后小问题之间还有相互重叠的小问题。
由于子问题在分解大问题的过程中重复出现,我们可以用从下往上的顺序先计算小问题的最
优解并储存起来,再以此为基础求取大问题的最优解。
这个叫从上往下分析问题,从下往上求解问题(听起来好有道理)。
回到这个剪绳子的问题,第一刀切的时候有n-1种选择(1~n-1),由于不知道那种方法是最优解,
所以我们需要把所有可能的情况都尝试一遍,也就是f(n)=max(f(i),f(n-1)),0<i<n且i属于正整数。
2)贪婪算法
应用贪婪算法的时候,每一步都可以做当前最贪婪的选择,
当把长度为n的绳子剪成长度为k和n-k的绳子时,f(k)=k(n-k)-n=-k^2+n*k-n
如果需要保证剪短之后乘积更大,即k(n-k)>n,应该存在k0使得f(k0)>0。
由二次函数图像知:f(k)max=f(k/2)=(n/4-1)*n,也就是说应该在n>=5的时候才应该剪短,否则是得不偿失的。
当n>=5时,有3*(n-3)>n和2*(n-2)>n,且1*(n-1)<n,4*(n-4)<n,结合函数图像不难推广到全体整数,
所以此时应尽可能多的剪成长度为3或者2的绳子段。
那么现在就要用到贪婪算法了,既然只能剪成3或2,那我就全给他剪成3。
但是如果最后剩下的刚好是长度4,剪成了3+1,得不偿失,这个时候需要接回去,再剪成2*2的。(因为至少剪一刀)
其实动态规划和贪婪算法是非常重要的算法,比如最小生成树的Kruskal和Prim算法,以及单源多点最短路径的
Dijkstra算法都用到了贪婪算法,就是我差不多全忘完了,现在就还记得名字和大概步骤...
应该早点开始写博客和github把这些东西全记录下来的,唉,都怪当初太年轻。
c/c++:
// ====================动态规划==================== int maxProductAfterCutting_solution1(int length) { if (length < 2) return 0; if (length == 2) return 1; if (length == 3) return 2; int* products = new int[length + 1]; products[0] = 0; products[1] = 1; products[2] = 2; products[3] = 3; //依次寻找从i到length的最优解 int max = 0; for (int i = 4; i <= length; i++) { max = 0; //寻找i的最优解 for (int j = 1; j <= i / 2; j++) { int product = products[j] * products[i - j]; if (product > max) max = product; } products[i] = max; } max = products[length]; delete[] products; return max; } // ====================贪婪算法==================== int maxProductAfterCutting_solution2(int length) { if (length < 2) return 0; if (length == 2) return 1; if (length == 3) return 2; //尽可能多的减去长度为3的绳子 int timesOf3 = length / 3; //如果最后是4剪成了3+1,再给他接回去 //因为2*2>3*1 if (length - timesOf3 * 3 == 1) timesOf3 -= 1; //剩下的都给剪成长度为2的绳子 int timesOf2 = (length - timesOf3 * 3) / 2; return (int)(pow(3, timesOf3)) * (int)(pow(2, timesOf2)); }
我猜肯定有人和我想的一样,直接把(int)(pow(2, timesOf2))改写成4多好,还算啥啊,浪费时间。
那你真是too young too simple,sometimes naive。
当绳子长度为5,先剪成3,然后剩下了2,(int)(pow(2, timesOf2))计算的结果就是2而不是4.
虽然就这么一种例外情况,但该写的还是要写滴,当然你修改一下return语句简化计算也不是不可以。