给你一个 m * n
的矩阵 seats
表示教室中的座位分布。如果座位是坏的(不可用),就用 '#'
表示;否则,用 '.'
表示。
学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的一起参加考试且无法作弊的最大学生人数。
学生必须坐在状况良好的座位上。
示例 1:
输入:seats = [["#",".","#","#",".","#"],
[".","#","#","#","#","."],
["#",".","#","#",".","#"]]
输出:4
解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。
示例 2:
输入:seats = [[".","#"],
["#","#"],
["#","."],
["#","#"],
[".","#"]]
输出:3
解释:让所有学生坐在可用的座位上。
示例 3:
输入:seats = [["#",".",".",".","#"],
[".","#",".","#","."],
[".",".","#",".","."],
[".","#",".","#","."],
["#",".",".",".","#"]]
输出:10
解释:让学生坐在第 1、3 和 5 列的可用座位上。
提示:
seats
只包含字符'.' 和``'#'
m == seats.length
n == seats[i].length
1 <= m <= 8
1 <= n <= 8
解题思路
首先不难想到组合做法,也就是枚举每个座椅放
和不放
两种状态,然后最后求最优解。类似问题Leetcode 39:组合总和
class Solution:
def maxStudents(self, seats: List[List[str]]) -> int:
r, c = len(seats), len(seats[0])
data = [(i, j) for i in range(r) for j in range(c) if seats[i][j] == '.']
n = len(data)
fan = set()
def dfs(u):
if u == n:
return 0
res = dfs(u + 1) # 不妨椅子
i, j = data[u]
for x, y in [[0, -1], [0, 1], [-1, -1], [-1, 1]]:
nx, ny = i + x, j + y
if 0 <= nx < r and 0 <= ny < c and (nx, ny) in fan:
break
else:
fan.add(data[u])
res = max(res, dfs(u + 1) + 1) # 可以放椅子
fan.remove(data[u])
return res
return dfs(0)
但是这么做的时间复杂度太高了。那么思考是不是有重叠计算?当前位置这一行最多可以坐多少人,由前一行的状态决定(和后一行没有关系)。有了这个依赖关系,我们可以通过记忆化去优化代码。
from functools import lru_cache
class Solution:
def maxStudents(self, seats: List[List[str]]) -> int:
r, c = len(seats), len(seats[0])
@lru_cache(None)
def dfs(x, y, pre, cur):
if x == r:
return 0
if not y:
pre, cur = cur, 0
nex, ney = (x, y + 1) if y < c - 1 else (x + 1, 0)
res = dfs(nex, ney, pre, cur)
if seats[x][y] == '#':
return res
for i, j in ((0, -1), (-1, 1), (-1, -1)):
nx, ny = x + i, y + j
if 0 <= nx < r and 0 <= ny < c and \
((nx == x and cur & 1 << ny) or \
(nx != x and pre & 1 << ny)):
break
else:
res = max(res, dfs(nex, ney, pre, cur | 1 << y) + 1)
return res
return dfs(0, 0, 0, 0)
其实当我看到这个问题的时候,首先想到的是将集合拆分成两个对立的集合,然后求最大的那个集合个数,这不就是二分图的最大匹配吗?这里我们将奇数列和偶数列看成对立面更好处理(注意不能将奇数行和偶数行看成对立面,原因在于题目的中的检测方向,横向可以向左向右,而纵向只能向上)。
在构建边的时候,通过偶数列的点指向奇数列的点。对于每个座椅可能存在6
条边(左侧、右侧、左上、右上、左下、右下),最后使用匈牙利算法即可。
需要注意的是,我们得到的最大匹配是不能摆放椅子的个数,最后的答案是总椅子数-最大匹配数。
class Solution:
def maxStudents(self, seats: List[List[str]]) -> int:
r, c = len(seats), len(seats[0])
match = [[-1] * c for _ in range(r)]
def find(node, vis):
x, y = node
for i, j in [[-1, -1], [0, -1], [1, -1], [-1, 1], [0, 1], [1, 1]]:
nx, ny = i + x, j + y
if 0 <= nx < r and 0 <= ny < c and not vis[nx][ny] and seats[nx][ny] == '.':
vis[nx][ny] = True
if match[nx][ny] == -1 or find(match[nx][ny], vis):
match[nx][ny] = node
return True
return False
res, cnt = 0, 0
for i in range(0):
for j in range(0, c, 2):
if seats[i][j] != '.': continue
vis = [[False] * c for _ in range(r)]
if find((i, j), vis): res += 1
for i in range(r):
for j in range(c):
if seats[i][j] == '.': cnt += 1
return cnt - res
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!