目录
1. 问题描述
人们聚集在某个活动会场上,根据到场顺序排成一排等待入场,活动的主办人员,想把人们从队列的某个位置分成两组,想要让分开的两组里至少有一组(原题是每一组)的男女人数都均等。但如果到场顺序不对,可能出现无论怎么分,两组都不能出现男女均等的情况。
举个例子,有3位男性、3位女性以“男男女男女女”的顺序到达,如图所示,无论从队列的哪个位置分开,两组的男女人数都不均等。但如果到场的顺序为“男男女女男女”,那么只需要在第4个人处分组就可以令分开的两组男女人数均等了。
求男性20人、女性10人的情况下,有多少种到场顺序会导致无论怎么分组都没有任何一组的组内男女人数能够均等?(原书中表述的有问题,因为如果要求两组中男女人数都相等,男女总人数就必然相等。而本题题设就是男女人数不等,这样无论按什么顺序到来都满足条件,过于trivial的问题)。
2. 解题分析
按照动态规划的思路进行子问题分解。
令男女人数分别为M(Male)和F(Female),令当前已经到达的男生和女生人数分别为m和f。记这种情况下接下来满足题设要求的人员到来顺序数为g(m,n)。
接下来要么来男生,要么来女生:
(1) 如果来男生的话,则问题变成了子问题g(m+1,f)
(2) 如果来女生的话,则问题变成了子问题g(m,f+1)
因此可以得到递推关系式:
接下来要确定边界条件。
Case1: 如果在某个时刻,到达的男生人数m和女生人数f相等,就相当于找到一个可以“均等分割”的情况(不符合题设且无需继续搜索),因此应该返回0。但是需要注意的是,必须在m>0的条件下以上论断才有效,否则的话,一开始就是m=n=0的情况,这个没有意义。
Case2: 如果在某个时刻,尚未到达的男生人数M-m和尚未到达的女生人数F-f相等,也就相当于找到一个可以“均等分割”的情况(不符合题设且无需继续搜索),因此应该返回0。同样需要注意要在(M-m)>0的条件下以上论断才有效。
Case3: 前两种情况都是属于搜索失败的情况,接下来确定搜索成功的情况。第一感是男生已经到齐而还没有碰到可以平均分割,或者女生已经到齐而还没有碰到可以平均分割,都是属于找不到“均等分割”的情况。但是这里有漏洞。比方说,M=20,F=10,女生已经到达10人,男生只到了9人,此时虽然尚未满足“均等分割”,但是接下来再来一个男生就凑出“均等分割”了。所有还得在以上条件的基础上加上一个约束条件,即任一方(记为A方)已经到齐而另一方((记为B方))到达的人数也不少于A方时,此时没有到达“均等分割”条件,后续只有B方人员到来,就不可能会再出现“均等分割”的情况了。
以下用递归+memoization的方式实现(无它,就是这个代码写起来简单,哪怕效率低一些也值得^-^).
3. 代码及测试
# -*- coding: utf-8 -*-
"""
Created on Sun Aug 29 07:14:15 2021
@author: chenxy
"""
import sys
import time
import datetime
# import random
from typing import List
# from queue import Queue
# from collections import deque
class Solution:
def noEqualPartition(self, M:int, F:int) -> int:
"""
:M: The number of boys
:F: The number of girls
:ret: The number of incoming order for no-equal-partition.
"""
memo = dict()
# Recursion core:
# Return the number of incoming orders which would satisfy the condition,
# when the number of already arrived boys and girls are m and f respectively
def recursion(m,f):
# print('m,f=',m,f)
if (m,f) in memo:
return memo[(m,f)]
if (m==f) and m>0:
memo[(m,f)] = 0
return 0
if (M-m)==(F-f) and (M-m)>0 : # Found equal partition
memo[(m,f)] = 0
return 0
if (M==m and f>=m) or (F==f and (m>=f)):
memo[(m,f)] = 1
return 1
sum = 0
if M-m >= 1:
sum += recursion(m+1,f)
if F-f >= 1:
sum += recursion(m,f+1)
memo[(m,f)] = sum
return sum
return recursion(0,0)
if __name__ == '__main__':
sln = Solution()
M = 20
F = 10
tStart = time.time()
ans = sln.noEqualPartition(M,F)
tCost = time.time() - tStart
print('M,F={0},{1}, ans={2}, tCost={3:6.3f}(sec)'.format(M,F,ans,tCost))
运行结果:
M,F=20,10, ans=2417416, tCost= 0.000(sec)
4. 思考
以上解题过程中其实是磕磕碰碰的,就是说一开始有些细节没有完全想到,导致结果不完全一致。然后可能要经过多次修正才能得到最终正确的答案。这是在已经知道答案的条件下可以这样做。如果没有现成的正确答案呢?那如何来判断吭哧吭哧得到的解答是正确的呢?这个可能是每一个人多多少少都会碰到的问题。有些问题在规模很小的比较容易用纸和笔计算出答案用于程序测试验证,但是并不总是有这么happy的情况,这种情况下怎么办呢?
另外原书以及其它博客中提到最多的估计就是最短路径解法了。。。这个后面再回头补充,先把每一道题都过一遍要紧。
上一篇:Q08: 优秀的机器人
下一篇:Q10: 轮盘的最大值
本系列总目录参见:程序员的算法趣题:详细分析和Python全解