《编程之美》读书笔记(四)——数组分割

来源:互联网 发布:知柏地黄丸吃多少粒 编辑:程序博客网 时间:2024/05/21 09:22


题目分析:题意转化为找出n个元素组成的子数组,使得其求和尽可能接近SUM/2 (SUM是2n个元素之和)

解法一:呃。。还真没想过这样解,跳过。

解法二:将目标进一步缩小到小于SUM/2但是尽可能接近SUM/2。
算法思路也比较直接,动态规划呗。
分析题目可以知道,其实题目中蕴含两个维度:一是全集2n个元素,二是子集n个元素。
所以可以设定第一维度k,限定元素全集从1个元素,逐渐地扩张到2n个元素;
当元素全集确定的情况下确定第二维度i,子集中元素个数从1个,逐渐地扩张到min(k,n)个。
当然,因为存在最优子结构【全集k+1下的情况包含了全集k的情况,并且能容易地从全集k的情况扩展到全集k+1】,所以可以使用动态规划。

形式化来表达就是,{S(k,i)}:((前k个元素中由i个元素组成的集合)之和的取值)的集合
递推公式为{S(k,i)} = {S(k-1,i)} U {vi + arr[k] | vi 属于{S(k-1,i)}且vi+arr[k]<SUM/2}

解法二的问题在于,k每增加1,集合就"几乎"翻倍增加,需要检查一遍上次迭代结束后剩余的所有情况。所以每一步的操作次数上限是k的指数次。因此总体算法复杂度上限是O(2^(2n)),即O(4^n)。
但是,这个界是不紧致的,因为每一步的每个操作后都需要检查新得到的元素是否超过SUM/2或者已经存在于现有集合当中,因此一步结束后,集合并不一定“翻倍”,而是存在一个SUM/2的界。

解法三:怎样才能让第k步的开销与k不再成指数关系。算法三取了个巧,不去维护具体的集合,而是采用数组方式,直接记录x个元素之和为y的布尔信息。省去了维护堆的开销,而且在O(1)时间内查找到某个新加元素是否已经存在于现有的集合中。
算法中第二层循环,在本书第一版是递增,这是个bug,到第二版改成了递减。因为,如果用递增的话,会引起“连锁”效应,同一个arr[k]被加入了多次。如果是倒序的话,则可以避免。


总的来说,这道题是比较典型的动态规划题目,前面也说了思路。还是不得不赞这种思想方法,原问题本身没有什么约束,动态规划自己往两个维度上增加了约束,得到了模式化的从小问题到大问题的扩展方法。