目录
1. 问题描述
有分别标了数字1~n的两副牌,共2n张。把这些牌排成一排,确保两张1的中间夹一张牌,两张2的中间夹2张牌,。。。,两张n的中间夹n张牌。
请问,当n=11时,有多少种排列方法?
2. 解题分析
考虑有2n个位置,按顺序地且按照以上规则的约束,安放每个数字的两张牌,直到2n张牌将2n个位置占满。这样的话这个问题可以看成(转换成)图搜索问题中的路径遍历问题。即从“空”状态开始到达“满”状态的所有不同路径的数量。这可以通过深度优先搜索来解决。
但是要注意的是,这里的终止状态并不是唯一的,所有的“满”状态都是终止状态。所以这不是从图中的某个起始节点到另一终止节点之间的路径遍历,而是遍历搜索从“空”状态节点到达所有合法的“满”状态节点的可能路径。
深度优先路径遍历搜索的要点如下所述。
2.1 节点表示
以长度2n的数组表示,“空”状态指数组中的元素为全“0”。“满”状态指数组中的元素全部为非零的状态,但是并非所有的“满”状态都是合法的状态。但是在本题中单独的状态还不能完全表示一个节点,还要加上接下来要插入的数据才能足以唯一地表示节点信息,即{state, m}。
2.2 邻节点搜索
基于当前状态curState和接下来要插入的数据m,可以按如下方式确定邻节点:从数组头部开始搜索,搜索到两个相隔m个位置都为空的时候,即可认为找到一个合法的邻节点{newState, m+1},newState为将m插入前述两个位置,而接下来要插入的数也相应地更新为m+1。
2.3 Memoization
3. 递归实现:从小到大插入
import sys
import time
# import random
# from typing import List
# from queue import Queue
# from collections import deque
class Solution:
def numberArrangement(self, N: int) -> int:
"""
:N:
:ret: The number of arrangement
"""
start = 2*N*[0] # Nested list to represent state/node
def explore(curState, num):
"""
:curState: current state to explore
:num: the number to be arranged
:ret: the number of arrangements
"""
# print('explore({0}, {1})'.format(curState, num))
# Judge whether reach the goal or final state.
if num > N:
return 1
sum = 0
for k in range(0,2*N-num-1):
if curState[k]==0 and curState[k+num+1]==0:
# nxtState = curState # Both points to the same object! NG
nxtState = curState.copy()
nxtState[k] = num
nxtState[k+num+1] = num
sum += explore(nxtState,num+1)
return sum
return explore(start,1)
if __name__ == '__main__':
sln = Solution()
for N in range(2,12):
tStart = time.time()
ans = sln.numberArrangement(N)
tCost = time.time() - tStart
print('N={0}, ans = {1}, tCost = {2}(sec)'.format(N,ans,tCost))
运行结果:
N=2, ans = 0, tCost = 0.0(sec)
N=3, ans = 2, tCost = 0.0(sec)
N=4, ans = 2, tCost = 0.0(sec)
N=5, ans = 0, tCost = 0.0(sec)
N=6, ans = 0, tCost = 0.0010356903076171875(sec)
N=7, ans = 52, tCost = 0.0029900074005126953(sec)
N=8, ans = 300, tCost = 0.02193927764892578(sec)
N=9, ans = 0, tCost = 0.1495983600616455(sec)
N=10, ans = 0, tCost = 1.1013171672821045(sec)
N=11, ans = 35584, tCost = 8.87007999420166(sec)
令人意外的是,N=5,6,9,10居然没有合法的安排方式!是程序的bug吗(N=11的结果与原书的结果一致,所以应该概率比较低)?如果是正确的运行结果的话,那么有没有可能从数学上证明满足什么条件才能有合法的安排方式呢?
另外,运行时间略长,需要进一步优化。
4. 递归实现:从大到小插入
To be added
5:递归实现:二进制表示
To be added
6:非递归实现
To be added
本系列总目录参见:程序员的算法趣题:详细分析和Python全解