注意,本问题不区分人,只考虑各个座位被占用的不同顺序的个数。
每个人选座位时,首先看有没有“超空位”(两边都没有挨着人的空位暂称为“超空位”),如果有则优先从“超空位”中选取。如果没有超空位,则从空位中任选一个坐下(空位是一定有的,因为人数和座位数是相等的)。
由于车厢中的作为安排方式(对面两排),两头两位的座位有一边是一定不挨着人的。如何表达才最方便于实现呢?
借用棋盘问题中的围栏思想,将两排座位排成一排,但是两头和中间各插入一个dummy座位(不能坐人),这样连同dummy座位总共有15个,可以表达为长度为15的数组。Dummy座位置为”-1”表示不能坐人,正常座位初始化为“0”,被人占据了则复制为“1”。这样,判断一个正常座位是否为“超空位”,只要看它本身是否为0且两边都不是1(即为“0”或者“-1”)。
考虑已经有k个座位被占了,从当前状态出发往后的剩下的(12-k)个座位被占的顺序数只与当前这个k个座位被占的状态有关,而与它们被占的顺序无关。因此这个可以用动态规划的策略来解决。本题考虑用top-down的方式(递归+memoization)来实现动态规划策略。
算法流程如下:
# -*- coding: utf-8 -*-
"""
Created on Sun Oct 17 10:52:39 2021
@author: chenxy
"""
import sys
import time
import datetime
import math
# import random
from typing import List
# from queue import Queue
# from collections import deque
import itertools as it
import numpy as np
memo = dict()
def isFull(pos):
return np.all(pos[1:7]) and np.all(pos[8:14])
def search(pos):
if tuple(pos) in memo:
return memo[tuple(pos)]
if isFull(pos):
return 1
cnt = 0
hasSuperEmpty = False
# Search super empty seat
for k in [1,2,3,4,5,6,8,9,10,11,12,13]:
if pos[k]==0 and pos[k-1]!=1 and pos[k+1]!=1:
pos[k] = 1
cnt = cnt + search(pos)
hasSuperEmpty = True
pos[k] = 0
if not hasSuperEmpty:
for k in [1,2,3,4,5,6,8,9,10,11,12,13]:
if pos[k]==0:
pos[k] = 1
cnt = cnt + search(pos)
pos[k] = 0
memo[tuple(pos)] = cnt
return cnt
N = 12 # Can't modify currently
pos = np.zeros(N+3)
pos[N//2+1] = -1
pos[N+2] = -1
tStart = time.perf_counter()
count = search(pos)
tCost = time.perf_counter() - tStart
print('count = {0}, tCost = {1:6.3f}(sec)'.format(count,tCost))
运行结果:count = 14100480, tCost = 0.032(sec)
轻松搞定。
跳过了前面一些题目先做这道,是因为浏览了一下“高级篇”中的题目,大多数题目想2~3分钟感觉没有头绪,先捏这种一看就觉得“顺眼”的软柿子捏一捏^-^。这种策略对于学习很重要,盲目地死磕一道题目花费太久时间容易产生挫折感从而打击学习的积极性。有些问题一时没有头绪,放在脑袋里供闲暇时间想一想说不定更容易突然就有什么灵感。。。