抽象出来大概是这样一个题:
给出一个数组,要求分成两份,要求两份和的差值最小.
面试官说想让我穷举每一种情况,看一下我程序设计的思维如何.
假如一共有32个数字,那我用一个32位二进制来枚举.如果第i位上的二进制为0,表示这个数字放到第0号集合,为1表示放到1号集合.
则:将这个32位整数从0加到0xFFFF FFFF就能找出所有情况.
当然,还可以用搜索,,比如DFS来做,不过有点大材小用了.上面说的二进制模拟应该是最优解了.
这也可以叫数位dp吧.
答:那就用三进制.
考虑动态规划.
假设所有数字加起来为sum,
两个数列要求和最相近,那就转化为小的那个数列非常接近sum/2,,也就是不超过sum/2的条件下尽量大.
那我们来复习一下01背包是个什么问题:
有n个石头,每个石头都有自己的重量w和价值v,现在你有一个能装m重量的袋子,请问怎么选取让价值v尽可能的大.
现在的问题是:
有n个数字,每个数字都有自己的大小, 现在你有一个能装sum/2数值的数列,请问怎么选取让重量尽可能的大.
对比一下问题,发现:
数字仅仅没有价值而已,而且数列容量上限就是数字大小的和,,那我们可以使数字的价值等于它本身的大小.这样两个模型就变得一模一样.
然后01背包问题的解法:
设d[i,j]表示前i个石头,袋子容量为j的情况下最多能获取多少价值.
设第i个石头的重量为wi,价值为vi
dp[i,j]=max( 放第i个石头可以得到的最大价值,前提是第i个石头可以放进去 ;
不放第i个石头可以得到的最大价值 )
=max( dp[i-1,j-wi]+vi , dp[i-1, j])
我们发现,对于状态的第一维:
第i个状态只跟第i-1个状态有关, 显然我们可以将O(n*n)的空间复杂度压缩成O(2n)的空间复杂度(两个数组倒来倒去就行了).
然后方程就砍掉了一维i
方程变为: dp[j] = max( dp[j-wi]+vi , dp[j]) ; 但是还是两层循环,i标只是不出现在方程里了,该循环还得循环(没有时间上的优化)
进一步优化:
我们甚至可以不用两个数组,只用一个数组:
考虑到第二维: 第j状态只跟第0,1....j-1,j状态有关,跟比j大的状态无关.所以满足所谓的无后效性.
这样我们可以倒着求dp[j],只用一个数组.
dp[j] = max( dp[j-wi]+vi , dp[j]) ; //其中i正向循环(这个i的顺序其实无所谓),j从大到小循环(必须的)
空间只需要一个一维dp数组就行了.
这个就不说了吧.