程序员的算法趣题Q53: 同数包夹

Posted 笨牛慢耕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了程序员的算法趣题Q53: 同数包夹相关的知识,希望对你有一定的参考价值。

目录

1. 问题描述

2. 解题分析

2.1 节点表示

2.2 邻节点搜索

2.3 Memoization

3. 递归实现:从小到大插入

运行结果:

4. 递归实现:从大到小插入

5:递归实现:二进制表示

6:非递归实现


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全解

以上是关于程序员的算法趣题Q53: 同数包夹的主要内容,如果未能解决你的问题,请参考以下文章

程序员的算法趣题Q14: 国名接龙

程序员的算法趣题Q39: 反复排序

程序员的算法趣题Q56: 鬼脚图中的横线(思路2的Python题解)

程序员的算法趣题Q35: 0和7的回文数

程序员的算法趣题Q69: 蓝白歌会

程序员的算法趣题Q10: 轮盘的最大值