摘要:  使用Python在给定整数序列中找到和为100的所有数字组合。可以学习贪婪算法及递归技巧。

难度:  初级

问题

  给定一个整数序列,要求将这些整数的和尽可能拼成 100。

  比如 [17, 17, 4, 20, 1, 20, 17, 6, 18, 17, 12, 11, 10, 7, 19, 6, 16, 5, 6, 21, 22, 21, 10, 1, 10, 12, 5, 10, 6, 18] , 其中一个解是 [ [17, 17, 4, 20, 1, 17, 6, 18] [20, 18, 17, 12, 11, 10, 6, 6] [7, 19, 16, 5, 6, 21, 10, 1, 5, 10] ] , 没有用到的数字是 [12, 10, 21, 22]

累加性的序列问题,通常可以使用动态规划法。不过这个略有不同,因为和为 sum 与 和为 sum-1 的解基本没有联系; 此题将使用贪心算法,每次总是根据当下找到一个数字组合来拼成 100 , 而不顾及使用的数字对后面拼100 造成的影响;因为不会穷尽所有可行解集合,所以不确保一定是最优解(未使用的数字最少)。

  算法设计

STEP1:  从序列 origin 中取出一个数 NUM ;

STEP2:  在剩下的数中寻找和为 100 - NUM 的数字组合 matched = m1,m2,..., mN ;

     A.  如果找到, 那么在 origin 中移除 NUM 和 matched ,  将 [matched, NUM] 加入匹配解集合 finalResults,  然后跳转到 STEP3 ;

B.  如果没有找到, 将 NUM 加入未匹配序列 unMatched, 跳转到 STEP3 ;

STEP3:   检测 origin 是否还有元素; 如果还有元素, 跳转到 STEP1 ; 否则跳转到 STEP4

STEP4:   退出算法。

  

  注意到这里需要一个函数 matchSum,实现以下功能: 给定一个数字 sum 及一个序列 seq, 如果能够在 seq 中找到和为 sum 的数字组合 matched,则返回 (True, matched) ; 否则返回 (False, []) .  这个函数可以递归实现。递归的三要素是: (解结构,终止条件, 递归方式)。如果解结构是一个无序序列,那么可以直接将解结构传入递归函数,在递归函数中逐步添加解结构的组成元素; 如果解结构是有序序列,则要注意递归添加解元素确保其顺序。

A0.  设置解结构 matched , 传入递归函数 matchSum(sum, seq, matched) , 在递归调用 matchSum 的过程中在 matched 中添加解元素;

   A1.  规定终止条件。通常就是最简单的情况,为空或只有一个元素。

     a1.  如果 sum > 0 , seq 为空,那么必然不存在匹配,返回 False;

a2.  如果 sum > 0, seq 仅含有一个数字, 且 seq[0] != sum, 那么仍然不存在匹配,返回 False ;

a3.  如果 sum > 0, seq 仅含有一个数字, 且 seq[0] == sum, 那么存在匹配,将 seq[0] 加入 matched, 返回 True;

A2.  设置递归方式。 seq 非空, 且含有两个及以上元素,该如何递归呢? 可以将 sum 与 seq[0] 比较,分情况讨论:

     a1.  如果 sum < seq[0] , 那么很显然 sum 只能在除去 seq[0] 之外的序列 seq[1:] 中寻找匹配了: matchSum(sum, seq, prematched) =  matchSum(sum, seq[1:], prematched) ;

     a2.  如果 sum == seq[0] , 那么很显然找到了一个匹配, 可以直接返回: matchSum(sum, seq, prematched) =  seq[0] + prematched ;  注意到, seq[0] 并不一定是指初始序列的第一个值,而是指在递归过程中获取到的当前序列的第一个值,而 prematched 在之前的递归过程中可能已经填充部分数字了;

     a3.  如果 sum > seq[0],就分两种情况,要么匹配结果包含 seq[0], 为 seq[0] + matchSum(sum-seq[0], seq[1:], prematched) ; 要么匹配结果不包含 seq[0], 为 matchSum(sum, seq[1:], prematched) .

解结构是一个列表,每个列表元素是一个小的列表,其元素之和为 100 ; 比如 [[54,36,10], [32, 63, 5], [24, 20, 34, 6, 12, 4]] , 未匹配数字为 [16, 20, 32] ; 

更优的解法

在算法结束时,很可能存在未匹配数字组合。尤其是当所有整数的和不能除尽100时。

将拼成100的数字组合中的数字替换成未匹配数字的组合,可以减少未匹配数字的数量。比如解结构里的一个元素是 [54, 36, 10] , 未匹配数字为 [16, 20, 32] , 那么可以将 36 与 16, 20 替换。

    更多的解法: 将解结构的子列表中的大数字与小数字组合交换,即可得到新的解法,比如将 54 与 (20,34) , 10 与 (6,4) , 32 与 (20,12) 交换。

    这些都会用到 matchSum(sum, seq, matched) , 可以说这个函数是核心而基本的组件。通过这个组件可以衍生出各种的方法和解法。这就像业务开发中的基础接口,通过基础接口的叠加可以衍生出多样灵活的业务。

应对大数据量: 当序列中数字比较多,且远小于要拼成的和时,就会出现递归深度很深的问题。Python 中有递归深度的限制。一种方法是提高递归深度设置;一种是将递归改写成非递归;一种是分治法,将大数字拆解为较小数字之和,在序列中先寻找较小数字的解结构,然后再拼接出最终的解结构。问题是,较小数字的选取很难确定,因为序列中可能根本不存在和为某一个较小数字的数字组合。

使用非递归方式, 可以考虑预排序。如果预排序可以使得后续处理更快速,那么算法复杂度就可能是 O(nlogn); 可以仍然按照之前的算法流程, 编写非递归的实现接口。注意到下面的实现是非优化的。取序列元素,只要和小于指定数,就一直添加; 当和为指定数时,就可以输出解;而当和大于指定数时,要考虑回溯。下面的实现仅考虑了最简单的情形的处理, 比如,序列为 [12,13,14,14,16,18,18,20,20,20,21,...] ,要构造和为 76 ; 假设已经添加的元素为 [12,13,14,14,16] ,  sum = 69 ,还差 7 , 那么就用后面的序列中的数替换前面的数 [12,13,14,14,16] + 7 = [19, 20, 21, 21, 23] , 只要后面的序列存在这几个数之一,替换后就可以输出解,比如 [12,20,14,14,16] . 但后面的序列不存在这几个数,就不会输出解。要么添加逻辑严密的回溯算法,要么添加更完备的处理方法。

代码实现

import random
import sys
sys.setrecursionlimit(10000) UPLIMIT = 100000
NUMBERS = 500
NUMBER_RANGE = (1,UPLIMIT/4) def randNums(n):
return [randGen(NUMBER_RANGE[0], NUMBER_RANGE[1]) for i in range(n)] def randGen(start, end):
return random.randint(start, end) def solve(numlist):
if UPLIMIT >= 10000 and NUMBERS >= 500:
numlist.sort()
(finalResults, unMatchedNums) = baseSolve(numlist, matchSumUnrec)
else:
(finalResults, unMatchedNums) = baseSolve(numlist, matchSum)
improve(finalResults, unMatchedNums)
return (finalResults, unMatchedNums) def baseSolve(numlist, matchSum):
if not numlist or len(numlist) == 0:
return ([],[]);
finalResults = []
unMatchNums = []
while len(numlist) > 0:
num = numlist.pop()
matched = []
if matchSum(UPLIMIT-num, numlist, matched):
removelist(numlist, matched)
matched.append(num)
finalResults.append(matched)
else:
unMatchNums.append(num) return (finalResults, unMatchNums) def removelist(origin, toberemoved):
for e in toberemoved:
if e in origin:
origin.remove(e) def copylist(numlist):
return [num for num in numlist] def matchSum(rest, restlist, prematched): if rest > 0 and len(restlist) == 0:
return False if rest > 0 and len(restlist) == 1 and restlist[0] != rest:
return False if rest > 0 and len(restlist) == 1 and restlist[0] == rest:
prematched.append(restlist[0])
return True getone = restlist[0]
if rest > getone:
prematched.append(getone)
if matchSum(rest-getone, restlist[1:], prematched):
return True
else:
prematched.remove(getone)
return matchSum(rest, restlist[1:], prematched)
elif rest == getone:
prematched.append(getone)
return True;
else:
return matchSum(rest, restlist[1:], prematched) def matchSumUnrec(asum, sortedlist, matched): if asum > 0 and len(sortedlist) == 0:
return False if asum > 0 and len(sortedlist) == 1 and sortedlist[0] != asum:
return False if asum > 0 and len(sortedlist) == 1 and sortedlist[0] == asum:
matched.append(sortedlist[0])
return True tmpsum = 0
ind = 0
size = len(sortedlist)
while ind < size:
num = sortedlist[ind]
tmpsum += num
if tmpsum < asum:
matched.append(num)
ind += 1
continue
elif tmpsum == asum:
matched.append(num)
return True
else:
tmpsum -= num
break need = asum - tmpsum
tosearch = map(lambda x:x+need, matched)
restlist = sortedlist[ind:] for e in tosearch:
if e in restlist:
matched.append(e)
matched.remove(e-need)
return True return False def improve(finalResults, unMatchedNums):
for comb in finalResults:
for num in comb:
matched = []
if matchSum(num, unMatchedNums, matched) and len(matched) > 1:
print 'Improved: ' , num, ' ', matched
comb.remove(num)
comb.extend(matched)
unMatchedNums.append(num)
for e in matched:
unMatchedNums.remove(e)
if len(unMatchedNums) == 0:
return def printResult(finalResults, unMatchedNums, numlist): f_res = open('/tmp/res.txt', 'w')
f_res.write('origin: ' + str(numlist) + '\n')
f_res.write('averag: ' + str((float(sum(numlist))/len(numlist))) + '\n')
f_res.write('solution: ') usedNums = 0
finalNumList = []
for comb in finalResults:
f_res.write(str(comb) + ' ')
assert sum(comb) == UPLIMIT
usedNums += len(comb)
finalNumList.extend(comb)
finalNumList.extend(unMatchedNums)
f_res.write('\nUnMatched Numbers: ' + str(unMatchedNums) + '\n')
f_res.write('Used numbers: %s, UnMatched numbers: %d.\n' % (usedNums, len(unMatchedNums))) f_res.write('origin: %d , final: %d\n' % (len(numlist), len(finalNumList)))
for e in finalNumList:
numlist.remove(e)
if len(numlist) > 0:
f_res.write('Not Occurred numbers: ' + str(numlist)) f_res.close() def to100(numlist): newnumlist = copylist(numlist)
(finalResults, unMatchedNums) = solve(newnumlist) newnumlist = copylist(numlist)
printResult(finalResults, unMatchedNums, newnumlist) if __name__ == '__main__':
to100(randNums(NUMBERS))

  需要优化的地方:

  将递归的 matchSum 改成非递归版本的 matchSumUnrec .

Python实现在给定整数序列中找到和为100的所有数字组合的更多相关文章

  1. 【python cookbook】找出序列中出现次数最多的元素

    问题 <Python Cookbook>中有这么一个问题,给定一个序列,找出该序列出现次数最多的元素.例如: words = [ 'look', 'into', 'my', 'eyes', ...

  2. 128 Longest Consecutive Sequence 一个无序整数数组中找到最长连续序列

    给定一个未排序的整数数组,找出最长连续序列的长度.例如,给出 [100, 4, 200, 1, 3, 2],这个最长的连续序列是 [1, 2, 3, 4].返回所求长度: 4.要求你的算法复杂度为 O ...

  3. LeetCode 128 Longest Consecutive Sequence 一个无序整数数组中找到最长连续序列

    Given an unsorted array of integers, find the length of the longest consecutive elements sequence.Fo ...

  4. python 学习笔记(四) 统计序列中元素出现的频度(即次数)

    案例一:在某随机序例中,找到出现频度最高的3个元素,它们出现的次数是多少? from random import randint # 利用列表解析器生成随机序列,包含有30个元素 data = [ra ...

  5. python 小程序,在列表中找到所有指定内容的位置

    要求如下图所示:

  6. 【Python小试】计算蛋白序列中指定氨基酸所占的比例

    编码 from __future__ import division def get_aa_percentage(protein, aa_list=['A','I','L','M','F','W',' ...

  7. 笔试算法题(34):从数字序列中寻找仅出现一次的数字 & 最大公约数(GCD)问题

    出题:给定一个数字序列,其中每个数字最多出现两次,只有一个数字仅出现了一次,如何快速找出其中仅出现了一次的数字: 分析: 由于知道一个数字异或操作它本身(X^X=0)都为0,而任何数字异或操作0都为它 ...

  8. Python 基础知识(持续更新中)

    内置数据类型:     整型     浮点型     字符串     布尔值     空值 None     列表 list     元组 tuple     字典 dict     集合 set   ...

  9. c:找到出现次数最多的递增数字串

    如题,如何在一亿位整数组成的字符串中找到出现次数最多的递增数字串? 答案: #include <stdio.h> #include <string.h> #define MAX ...

随机推荐

  1. 应用Strong Name保存.NET应用程序集

    关于Strong Name的主题,网上已经有很多这方面的介绍,你可能最熟悉的印象就是这样 大部分的情况,这样就可以了.如果代码是机密的,还可能用到Delay sign only,这就复杂一些,请查找相 ...

  2. ELK到底是什么?那么多公司用!

    Sina.饿了么.携程.华为.美团.freewheel.畅捷通 .新浪微博.大讲台.魅族.IBM...... 这些公司都在使用ELK!ELK!ELK! ELK竟然重复了三遍,是个什么?   一.ELK ...

  3. linux下jmeter持续集成Jenkins部署时问题解决

    之前成linux下安装了Jenkins,并做了一些简单的工作,这次正好将jmeter也集成进去,在实际操作时发现好多坑,写下做记录 怎么安装这里就不介绍了,网上很多资料,这里只记录问题,以供大家参数 ...

  4. Unity3D笔记 愤怒的小鸟<五> 小鸟动画+Unity3D如何设置断点调式

    前言:实现小鸟的动画,之前吐槽过js写U3D,就改成了C#来写,没想到遇到问题了. 实现的效果 using UnityEngine; using System.Collections; /// < ...

  5. html 自动弹出框

    1.点击div外部隐藏, //*代表tip_box所包含的子元素 $('body').click(function(e) { var target = $(e.target); if(!target. ...

  6. Elasticsearch-mapper 基于注解方式生成mapping(2.0以上)

    Elasticsearch生成mapping的方式上有多种方式,我们可以把mapping做成配置文件,也可以用spring-data-elasticsearch基于注解生成. 在基于注解生成这种方式上 ...

  7. ElasticSearch 安装 go-mysql-elasticsearch 同步mysql的数据

    一.首先在Centos6.5上安装 go 语言环境 下载Golang语言包:https://studygolang.com/dl [hoojjack@localhost src]$ ls apache ...

  8. [分布式系统学习]阅读笔记 Distributed systems for fun and profit 之三 时间和顺序

    这是阅读 http://book.mixu.net/distsys/time.html 的笔记,是该系列的第三章. 为什么时间和顺序很重要呢?为什么我们关系事件A发生在事件B之前? 因为分布式系统要解 ...

  9. Saltstack之SSH

    salt-minion也可以不安装通过在master安装salt-ssh 1,安装 yum -y install salt-ssh 2,配置salt的花名册 vim /etc/salt/roster ...

  10. NLP常用语料集合

    常用语料资源 下面提供一些网上能下载到的中文的好语料,供研究人员学习使用.(1).中科院自动化所的中英文新闻语料库 http://www.datatang.com/data/13484中文新闻分类语料 ...