摘要:  使用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. 【JSP】JSP的介绍和基本原理

    JSP简介 JSP的核心实质是Servlet技术.JSP是后来添加的基于Servlet的一种扩展技术.但二者在使用上有不同的方向. 由于Servlet实质是一个Java类,因此非常适合用来处理业务逻辑 ...

  2. [工具] Citavi – 文献管理软件(笔记记录)

    https://www.citavi.com/en/ 如果你有论文写作的需求,特别是对于科研人员.研究生来说,文献管理软件是不可或缺的.目前流行的文献管理软件有老牌的 EndNote,本土化的 Not ...

  3. 基础笔记1(进制,浮点,递归,floor,round和ceil)

    1,进制默认是十进制 0开头 8进制 0x     16进制 0b     2进制 2,long 的范围是19位数字.int范围是21亿左右,short 是三万二千左右. 超过int范围的long类型 ...

  4. 基于pandas python的美团某商家的评论销售数据分析(可视化)

    基于pandas python的美团某商家的评论销售数据分析 第一篇 数据初步的统计 本文是该可视化系列的第二篇 第三篇 数据中的评论数据用于自然语言处理 导入相关库 from pyecharts i ...

  5. ubuntu下中文乱码解决方案(全)

    转自 http://www.cnblogs.com/end/archive/2011/04/19/2021507.html   1.ibus输入法 Ubuntu 系统安装后已经自带了ibus输入法,在 ...

  6. ABP之应用服务(1)

    在一个理想的层级项目中,展现层是不能直接访问领域对象的,那么展现层如何获取到自己需要的数据呢?也就是今天的主角-Application层,它的职责就是为展现层服务,它通过仓储获取到相应的数据,然后将数 ...

  7. php无限极分类递归与普通

    1. 递归 public function getInfo(){$data=$this->select();$arr=$this->noLimit($data,$f_id=0,$level ...

  8. easyui treegrid idField 所在属性中值有花括号(如Guid)当有鼠标事件时会报错,行记录一下

    easyui treegrid idField 所在属性中值有花括号(如Guid)当有鼠标事件时会报错,行记录一下

  9. 2.5BatchNormalzation

    BatchNormalzation是一种解决深度神经网络层数太多,而没有办法有效前向传递的问题,因为每层的输出值都会有不同的均值和方差,所以输出数据的分布也不一样. 如果对于输入的X*W本身得到的值通 ...

  10. POJ 1066 - Treasure Hunt - [枚举+判断线段相交]

    题目链接:http://poj.org/problem?id=1066 Time Limit: 1000MS Memory Limit: 10000K Description Archeologist ...