一道动态规划的题目

来源:互联网 发布:迅雷 for mac 老版本 编辑:程序博客网 时间:2024/05/17 19:59
题目描述:
Create a class called Football. In football, scores are incremented by either
2, 3, or 7 points. Given a numerical input (integer between 1 and 75)
representing a final score, calculate the number of all possible combinations
of (2, 3, 7) which add up to that score. The output should be the number of
combinations found. Here are a couple of examples:

input | output | combinations
1       0
4       1        (2, 2)
9       3        (2, 7), (2, 2, 2, 3), (3, 3, 3)
11      3        (2, 2, 7), (2, 2, 2, 2, 3), (2, 3, 3, 3)

Here is the method signature (be sure your method is public:
int fetchCombinations(int input)

We will check to make sure the input to this problem is valid.

其实就是一个根据输入的一个数和一个数组,计算出有多少种不同的组合方式使得只使用数组里面的数(可以重复)相加结果等于输入的数。

假设输入的数为11,数组为[2,3,7],那么输出3,表示有三种不同的组合方式,分别是(2, 2, 7), (2, 2, 2, 2, 3), (2, 3, 3, 3)

我的有两种方案:
     1、第一种使用的是强暴的递归方式,假设输入的数为N,数组为[a,b,c,d],那么调用递归函数的定义为recursion(int num, List<Integer> result);
     初始化的时候传入的list为空的,当num等于0的时候将result加入到结果集里面;如果小于0则说明这个result不是一种组合,直接返回;否则复制一份当前的数组内容,然后依次得将数组元素k加入到新创建的数组中,递归的调用函数recursion(num-k, new_result);这样递归完成之后就能够得到所有的结果集合了。
     2、使用和动态规划类似的方法:
     因为要计算一个数N的所有可能的组合,那么就相当于先计算出N-a、N-b,N-c,N-d的组合,然后分别向这些组合中都加入a、b、c或者d,但是计算子集合的时候可能存在一定的重复,所以可以使用一个map将计算的结果保存起来,然后在计算之前首先查看这个map就可以了。这个map的key是一个整数,value是一个二维数组,每一项是一个结果的组合。
     
     通过上面的方法可以得到由数组元素相加之和等于输入参数的所有集合,但是这些集合肯定存在着重复,例如11=2+2+2+2+3,11=3+2+2+2+2因为这里要计算组合的数目,这种重复需要去除,这时就需要一种方案去除所有重复的组合,在这里也就是判断两个数组是否包含相同的元素(即使它们的顺序不一样)。

这里我使用了两种方案:
     0、最普通的方案是直接比较,首先对两个数组排序,然后依次比较,时间复杂度为O(lgn)
     1.使用素数相乘的方法,因为任意多个素数的乘积结果不可能由其他任意多个素数相乘得到,利用这一点,我们可以给输入数组中的a,b,c,d分别赋予一个素数,这里从小到大依次是2,3,5,7,那么就需要计算两个数组中出现的a,b,c,d分别用这几个素数代替相乘,如果两个数组的计算结果相同,那么这两个数组肯定是相同的元素。但是这里存在移除的可能,我使用的是int保存乘积的结果,假设N等于100,a=2,此时a对应的素数是2,那么这个结果的乘积的结果就是2的50次方。显然已经溢出了,但是溢出不会影响正确性,只不过这里我们使用了2这个素数,如果碰巧连续出现多于31个2,那么肯定会溢出,溢出的结果是乘积等于0了,这就会出现错误了,所以我的解决方案是不使用2了,第一个素数使用3.当然也可以使用1,只不过在判断乘积是否相等之前需要先判断两个数组的元素个数是否相等。
     2、使用计数的方法,两个数组元素相同,也就等同于两个数组中a,b,c,d的出现次数是相同的。那么我只需要保存这些出现的次数,这显然比较整个数组要高效的多,我还利用java的hashcode方法根据数组中每个元素出现的次数得到该对象的哈希值,然后再equals方法再逐个比较出现的次数。这样可以保证正确性。

测试结果:这里输入的参数是50-60(因为大于60之后递归方式将慢的不可接受),输入的数组是[2,3,7]

1、使用递归的方式+使用素数去重的方案:
value = 50, result = 37, cost = 14499 msvalue = 51, result = 39, cost = 20057 msvalue = 52, result = 40, cost = 29952 msvalue = 53, result = 41, cost = 40306 msvalue = 54, result = 43, cost = 59413 msvalue = 55, result = 44, cost = 81259 msvalue = 56, result = 46, cost = 110723 msvalue = 57, result = 47, cost = 160055 msvalue = 58, result = 49, cost = 227401 msvalue = 59, result = 50, cost = 334724 msvalue = 60, result = 52, cost = 443120 ms



2、使用递归方式 + 计数去重的方案
value = 50, result = 37, cost = 14882 msvalue = 51, result = 39, cost = 21285 msvalue = 52, result = 40, cost = 29318 msvalue = 53, result = 41, cost = 40546 msvalue = 54, result = 43, cost = 71245 msvalue = 55, result = 44, cost = 82617 msvalue = 56, result = 46, cost = 111649 msvalue = 57, result = 47, cost = 157711 msvalue = 58, result = 49, cost = 211052 msvalue = 59, result = 50, cost = 292131 msvalue = 60, result = 52, cost = 408437 ms



3、使用非递归方式 + 素数去重的方案
value = 150, result = 290, cost = 230 msvalue = 155, result = 308, cost = 121 msvalue = 160, result = 328, cost = 252 msvalue = 165, result = 348, cost = 75 msvalue = 170, result = 369, cost = 104 msvalue = 175, result = 390, cost = 161 msvalue = 180, result = 412, cost = 86 msvalue = 185, result = 434, cost = 108 msvalue = 190, result = 457, cost = 97 msvalue = 195, result = 481, cost = 132 msvalue = 200, result = 505, cost = 157 ms



4、使用非递归的方式 + 计数去重的方案
value = 150, result = 290, cost = 290 msvalue = 155, result = 308, cost = 101 msvalue = 160, result = 328, cost = 98 msvalue = 165, result = 348, cost = 86 msvalue = 170, result = 369, cost = 139 msvalue = 175, result = 390, cost = 449 msvalue = 180, result = 412, cost = 87 msvalue = 185, result = 434, cost = 185 msvalue = 190, result = 457, cost = 113 msvalue = 195, result = 481, cost = 167 msvalue = 200, result = 505, cost = 132 ms



        从测试结果来看,使用非递归方式显然要比递归的方式性能高出几个数量级,递归存在了太多的重复计算了。而对于去重的方案,两种方案相差不大吧,使用计数方式更加可靠一些,这种方式不需要为正确性担忧。
        该程序还有更加优化的地方:采用非递归的方案的时候,可以将每一个数的计算放到多线程中,只不过要共享全局的缓存,这样应该可以提高性能。
        补充:其实这个和斐波那契的思想差不多,所以还有一种从底向上的解法,首先初始化给定数组中几个数作为总数的组合结果并缓存,然后依次增加计算,指导计算到最终的输入值,就可以得到结果,并且这种方案节省一定的内存,可以在运行的时候释放距离太远的缓存,例如输入是[2,3,7],当前计算到15,那么8之前的计算结果就不再需要,可以被释放了。

        再补充:经过测试发现多线程对于这种问题似乎没有任何的优势,因为我们在计算N的时候需要依赖N-K的结果,如果使用多线程,那么需要从小到大计算,但是在计算N的时候需要依赖于N-k1,N-k2,N-K3的结果,但是这三个值得计算可能分配到其他线程完成了,为了保证正确性就需要进行wait/notify操作,这会导致不如缓存情况下的计算,因此,多线程的效果比较糟糕。


代码地址:https://github.com/terry-chelsea/Algorithm/tree/master/src/tc/football
欢迎指正~~
0 0
原创粉丝点击