记录一个二进制求集合子集和01背包的变式.(阿里面试遇到了让我把01背包给忘了,不可忍)

抽象出来大概是这样一个题:

给出一个数组,要求分成两份,要求两份和的差值最小.

question one:请用穷举法来解

面试官说想让我穷举每一种情况,看一下我程序设计的思维如何.

假如一共有32个数字,那我用一个32位二进制来枚举.如果第i位上的二进制为0,表示这个数字放到第0号集合,为1表示放到1号集合.

则:将这个32位整数从0加到0xFFFF FFFF就能找出所有情况.

当然,还可以用搜索,,比如DFS来做,不过有点大材小用了.上面说的二进制模拟应该是最优解了.

这也可以叫数位dp吧.

问:如果是分成三份呢?

答:那就用三进制.

question two:那你用最好的方法来做

考虑动态规划.

假设所有数字加起来为sum, 

两个数列要求和最相近,那就转化为小的那个数列非常接近sum/2,,也就是不超过sum/2的条件下尽量大.

那我们来复习一下01背包是个什么问题:

有n个石头,每个石头都有自己的重量w和价值v,现在你有一个能装m重量的袋子,请问怎么选取让价值v尽可能的大.

现在的问题是:

有n个数字,每个数字都有自己的大小,                现在你有一个能装sum/2数值的数列,请问怎么选取让重量尽可能的大.

对比一下问题,发现:

数字仅仅没有价值而已,而且数列容量上限就是数字大小的和,,那我们可以使数字的价值等于它本身的大小.这样两个模型就变得一模一样.

然后01背包问题的解法:

1.表示状态:

设d[i,j]表示前i个石头,袋子容量为j的情况下最多能获取多少价值. 

2.寻找状态转移方程

设第i个石头的重量为wi,价值为vi

dp[i,j]=max(   放第i个石头可以得到的最大价值,前提是第i个石头可以放进去    ;

                      不放第i个石头可以得到的最大价值        )

         =max( dp[i-1,j-wi]+vi   ,     dp[i-1, j]) 

3.优化方程

我们发现,对于状态的第一维:

第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数组就行了.

4.考虑边界和结果

这个就不说了吧.

 

你可能感兴趣的:(动态规划dp,面试)