24點演算法

隨機取四個數字,用加、減、乘、除、括弧5種運算,計算結果等於二十四。

窮舉法

import itertools
 
# 括弧的組合只存在如下五種表達式結構 卡特蘭數
formats = ['(({0[0]}{1[0]}{0[1]}){1[1]}{0[2]}){1[2]}{0[3]}',
           '({0[0]}{1[0]}({0[1]}{1[1]}{0[2]})){1[2]}{0[3]}',
           '({0[0]}{1[0]}{0[1]}){1[1]}({0[2]}{1[2]}{0[3]})',
           '{0[0]}{1[0]}(({0[1]}{1[1]}{0[2]}){1[2]}{0[3]})',
           '{0[0]}{1[0]}({0[1]}{1[1]}({0[2]}{1[2]}{0[3]}))']

# 可選的操作序列 64種:4*4*4
def getOplist(ops):
    op_list = [ops[i]+ops[j]+ops[p] for i in range(4) for j in range(4) for p in range(4)]
    return op_list
    
opt_list = getOplist('+-/*')

    
def main_all():
    cnt = 0
    no_ans = 0
    # 可重複組合數 13C4 = 715
    for nums in itertools.combinations_with_replacement([1,2,3,4,5,6,7,8,9,10], 4):
        flag = False
        cnt += 1  
        ans = "no ans"
        
        for num in itertools.permutations(nums, 4): # 4個數的排列數4!=24
            for op in opt_list: # 操作序列 64種:4*4*4
                for f in formats: #括弧的組合 5種
                    try:
                        expr = f.format(num, op)
                        n = eval(expr)
                        # 這裡需要注意小數點的截斷
                        if 23.99 < n and n < 24.01:
                            flag = True
                            ans = expr
                            break
                    except ZeroDivisionError:
                        continue
                if flag:
                    break
            if flag:
                    break
          
        if not flag:
            no_ans += 1
        print((cnt,nums,ans))
    print(("all",cnt,"no ans",no_ans,"ans",cnt-no_ans))

main_all()                    

分析:窮舉一次的遍歷次數:\(4!*4^3*5=7680\),每次都是計算整個表達式,成本高。

下面的程式碼,讀取輸入,輸出表達式

def main_input():
    while True:
        try:
            nums, flag = input().split(), False
            for num in itertools.permutations(nums, 4):
                for op in opt_list:
                    for f in formats:
                        try:
                            expr = f.format(num, op)
                            n = eval(expr)
                            if 23.99 < n and n < 24.01:
                                flag = True
                                print(expr)
                                break
                        except ZeroDivisionError:
                            continue
                    if flag:
                        break
                if flag:
                        break
            if flag:
                print('true')
            else:
                print('false')
        except:
            break

main_input()

二元計算

把多元運算轉化為兩元運算。

先從四個數中取出兩個數進行運算,然後再從運算結果和其它兩個數組成的三個數中取出兩個數進行運算,最終得到兩個數,再次進行運算,得到最終結果。

在求表達式的過程中,最難處理的就是對括弧的處理,而這種思路很好的避免了對括弧的處理。

import itertools

def is24(nums):
    if len(nums) == 1:
        return 23.99 < nums[0] and nums[0] < 24.01

    for i in itertools.permutations(nums, 2):
        arr = nums.copy()
        arr.remove(i[0])
        arr.remove(i[1])
                 
        if is24(arr + [i[0] + i[1]]) or is24(arr + [i[0] - i[1]]) or is24(arr + [i[0] * i[1]]) or (i[1] != 0 and is24(arr + [i[0] / i[1]])):
            return True

    return False
 
def main_all():
    cards = 4
    cnt = 0
    ans = 0
    for num in itertools.combinations_with_replacement([1,2,3,4,5,6,7,8,9,10], cards):
        nums = list(num)
        if cards>=5 and all(data==nums[0] for data in nums):
            continue
        cnt += 1  
        if is24(nums):
            ans += 1
        else:
            print((nums,"no ans"))

    print(("all",cnt,"no ans",cnt-ans,"ans",ans))

main_all()    

分析:窮舉一次的遍歷次數:\(C_4^2*4*C_3^2*4*C_2^2*4=1152\),為窮舉法的15%。

缺陷是,需要額外的代價輸出計算表達式。

統計

選取4張牌,最大數字為10,組合數為715,其中149個組合無解;如果最大數字為13,組合數為1820,其中458個組合無解。

選取5張牌,最大數字為10,組合數為1992,其中37個組合無解;如果最大數字為13,組合數為6175,其中80個組合無解。

refer

24點遊戲里有哪些非常難算的題目?

//www.zhihu.com/question/264095014/answer/2503005870

24點遊戲演算法

//blog.csdn.net/hyblogs/article/details/106676393

所有1362個可解組合的所有獨立解

//www.4shu.net/solutions/allsolutions/

Tags: