0
点赞
收藏
分享

微信扫一扫

思路篇-递归与动态规划(3)

蚁族的乐土 2022-03-16 阅读 77

1,A,B玩家取纸牌牌,看获胜者分数的问题

2,村里的人相互收发信的方案数

3,N皇后问题的方案数

1,A,B玩家取纸牌牌,看获胜者分数的问题

给定一个整型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或者最右的纸牌,玩家A和玩家B都决定聪明。请返回最后获胜者的分数。

思路:

创建两个子函数,分别是在当前arr中,作为先手拿到的牌值和作为后手拿到的牌值,L和R指示当前纸牌左右的索引。作为先手的人有两个选择,一个选择拿左边,然后递归调用L+1到R中作为后手的最大纸牌值,一个是选择拿右边,然后递归调用L到R-1作为后手的最大纸牌值,这两个选择的纸牌值取最大数。作为后手的人在当前的arr中,只能被动的选择当前的先手在L+1到R中的,以及先手在当前的L到R-1中返回的最小值。

伪代码流程

fun_first(arr, L, R):
    if L==R:
        return arr[L]
    return max(arr[L] + fun_second(arr, L+1,R), arr[R]+ fun_second(arr, L, R-1))

fun_second(arr, L, R)
    if L==R:
        return 0;
    return min(fun_first(arr, L+1,R), fun_first(arr, L, R-1))

fun_main(arr):
    if arr==null || arr.length ==0:
        return 0
    
    return max(fun_first(arr, 0, arr.length-1), fun_second(arr, 0, arr.length-1))

2,村里的人相互收发信的方案数

一个村庄有n个人,每个人规定必须完成这个任务:给别人发一封信,并且收到一封信,请问完成任务可以有多少种方法?

思路

当n=1时候,有0种方法。

当n=2时候,有1种方法。即A发给B,B发给A

当n=3时候,有2种方法。即A发给B,B发给C,C发给A;A发给C,C发给B,B发给A

当n =5时候,假设是A,B,C,D,E 五个人,

A可以发给B,C,D,E任意一个人,假设分给了B,那么接下来分为两种情况,一是B发信给了A,此时还有n=3的方法数情况;二是B没有发信给A,那么接下来A需要收到一封信,B需要发出一封信,就等效于AB为一个新人,和C,D,E共同组成一个n=4的村庄的情况下方法数。

所以递归方法数为(n-1)* (f(n-1)+f(n-2))。

3,N皇后问题

在一个N * N的棋格上,任意行,任意斜线上只能有一个皇后数,求组成N皇后的棋盘摆放方案数。

思路

用 i 表示当前要摆放第几行,数组record有N个数,record[i]表示第i行的皇后放在了第几列,ans表示方案数。逐行确定第 i 行皇后可摆放位置,如果有多个,按顺序遍历。如果后面发现其它行没有可摆放的位置,表示当前行摆放位置不可取,回溯至此,遍历下一个摆放位置。

伪代码流程:


fun_process(N, i, record):
    if i==N:
        return 1
    
    ans =0 
    for j in range(N-1):
        if fun_isValid(record, i, j):
            record[i]= j
                ans +=fun_process(N, i+1, record)

    return ans


fun_isValid(record, i, j)://判断放在i,j位置会不会和i之前的行冲突
    for k in range(i):
        if (record[k] == j) or abs(i-k)==abs(record[k]-j):
            return False
    return True

fun_main(N):
    if N==1:
        return  0
    record=[0].zfill(N) //N 个0的数组
    ans =fun_process(N, 0, record)
    return ans

N 皇后问题的时间复杂度都是N**2,但是可以通过位运算,优化常数项的时间。

用limit是一个N位的数值,每个bit位置都是1表示最终要解决N行个皇后,列的限制用colLim也是一个N位的数,每位都用0,1表示,当bit=1表示该列受限,皇后不能放在这列了,leftDiaLim和rightDiaLim分别表示左斜线和右斜线的限制。当在colLim的某个bit放了皇后后,leftDiaLim是该(leftDiaLim | mostRightOne)<<1,mostRightOne表示colLim可放皇后的最右边那个bit位置。

两种方法的python代码如下,明显process2更快

# -*- coding: utf-8 -*-

import numpy as np
import time
def isValid(record,i,j):
    for k in range(i):
        if record[k]==j or abs(record[k]-j)==abs(i-k):
            return False
    return True

def process1(N,i, record):
    if i==N:
        return 1

    ans =0
    for j in range(N):
        if isValid(record, i,j):
            record[i]=j
            ans+=process1(N, i+1,record)
    return ans

def process2(limit, colLim, leftDiaLim, rightDiaLim):
    if (colLim ==limit):#所有位都排好了
        return 1
    #所有候选位置的皇后位置都在pos上
    pos =limit & (~(colLim | leftDiaLim | rightDiaLim))
    ans =0
    while(pos !=0):
        mostRightOne = pos & (~pos +1) #可放皇后的最右边的列位置
        pos =pos -mostRightOne
        ans+=process2(limit,colLim | mostRightOne,(leftDiaLim | mostRightOne)<<1,(rightDiaLim | mostRightOne)>>1)
    return ans




if __name__ =='__main__':
    N=9
    if N==1 :
        print('ans is :',0)
    else:
        record=np.zeros(N)
        t1=time.time()
        ans =process1(N,0,record)
        print ('process1 ans is %d,cost time is %f'%(ans, time.time()-t1))

        if N >32:
            print('not support')
        limit=-1  if  N==32 else (1<<N)-1
        t2=time.time()
        ans2=process2(limit,0,0,0)
        print('process2 ans is %d, cost time is %f'%(ans2,time.time()-t2))


举报

相关推荐

0 条评论