1386 安排电影院座位(字典、位运算)

1. 问题描述:

如上图所示,电影院的观影厅中有 n 行座位,行编号从 1 到 n ,且每一行内总共有 10 个座位,列编号从 1 到 10 。
给你数组 reservedSeats ,包含所有已经被预约了的座位。比如说,researvedSeats[i]=[3,8] ,它表示第 3 行第 8 个座位被预约了。
请你返回最多能安排多少个 4 人家庭 。4 人家庭要占据同一行内连续的4个座位。隔着过道的座位(比方说 [3,3] 和 [3,4])不是连续的座位,但是如果你可以将 4 人家庭拆成过道两边各坐 2 人,这样子是允许的。

示例 1:

输入:n = 3, reservedSeats = [[1,2],[1,3],[1,8],[2,6],[3,1],[3,10]]
输出:4
解释:上图所示是最优的安排方案,总共可以安排 4 个家庭。蓝色的叉表示被预约的座位,橙色的连续座位表示一个 4 人家庭。

示例 2:

输入:n = 2, reservedSeats = [[2,1],[1,8],[2,6]]
输出:2

示例 3:

输入:n = 4, reservedSeats = [[4,3],[1,4],[4,6],[1,7]]
输出:4

提示:

1 <= n <= 10^9
1 <= reservedSeats.length <= min(10*n, 10^4)
reservedSeats[i].length == 2
1 <= reservedSeats[i][0] <= n
1 <= reservedSeats[i][1] <= 10
所有 reservedSeats[i] 都是互不相同的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/cinema-seat-allocation

2. 思路分析:

① 一开始想到的是使用哈希表(字典)来记录每一行中被预约的位置,然后遍历记录每一行被预约位置的字典,依次判断有多少个连续的4个位置,从题目中可以知道当第1个位置与第10个位置是否被预约对结果都是没有什么影响的,所以当23456789这8个位置没有被预约的时候说明是这些位置是可以分配给两个家庭的,其余的情况只能够将座位分配给一个家庭:当2345、4567、6789这三种位置只要是满足一种没有被预约的情况那么就可以分配给一个家庭,因为使用的是python语言,所以我们在一开始的时候可以定义三个列表pos1, pos2, pos3 = [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9],在遍历字典的值的时候求解字典记录的被预约位置的list列表与上面定义的三个列表的交集来判断属于哪一种的情况,但是提交上去48个例子超时了(一开始的时候没有比较好的思路的情况下可以根据题目的描述模拟整个过程看是否可以解决)

② 在①中的方法超时之后,于是理解了一下力扣官网的题解,题解中使用的是位运算的方法,感觉思路真的是好棒,可以学习学习。分析题目可以知道第1个与第10个位置是否被预约对答案没有什么影响,所以我们可以使用8个二进制位(表示第2-9个位置)来表示哪些位置是被预约的,预约的位置可以标记为1,否则为0,我们可以使用字典来记录每一行中被预约的位置,也就是将预约的位置对应的二进制位置为1,因为第2-9个位置才是有效的(对结果才有影响),而二进制的8个位分别对应着第2-9个位置,所以我们在记录这些被预约的位置的时候需要先将预约的位置减去2(例如二进制的第0位为1表示第2个位置被预约了),然后使用1左移预约的位置减去2的数值那么就可以将被预约位置对应的二进制位置为1,比如第一行的第6个位置被预约了,那么应该将二进制的第4个位置置为1(二进制的位置从低位向高位算起),因为是要记录每一行中所有被预约的位置所以我们要将字典中的值进行或运算,这样可以保证一行中所有被预约的位置都置为1

③ 在②中我们使用字典记录了每一行被预约的位置对应的二进制数字,然后我们就可以遍历字典了,这里定义三个二进制数字,分别为0b11110000, 0b11000011, 0b00001111,这三个数字分别表示2345/4567/6789这些位置是没有被预约的,假如这些二进制数字与字典中每一行被预约位置的二进制数字进行或运算得到的结果还是这些数字说明是可以分配给一个家庭的:说明这些位置是没有被预约的,计数加1即可,此外我们还需要累加每一行中一个位置(可以分配两个家庭)都没有被预约的情况到结果中

总结:使用8位二进制数字表示第2-9个位置的预约情况,所以使用1 << (n - 2)将二进制数字对应的位置置为1(n为被预约的位置),然后将字典中的值对应的二进制数字与当前1左移(n - 2)位得到的数字进行或运算,或运算目的是保持每一行被预约的二进制位都是1

3. 代码如下:

字典(超时):

import collections
from typing import List


class Solution:
    def maxNumberOfFamilies(self, n: int, reservedSeats: List[List[int]]) -> int:
        dic = collections.defaultdict(list)
        for i in range(len(reservedSeats)):
            dic[reservedSeats[i][0]].append(reservedSeats[i][1])
        # print(dic)
        res = 0
        pos1, pos2, pos3 = [2, 3, 4, 5], [4, 5, 6, 7], [6, 7, 8, 9]
        for i in range(n):
            if i + 1 in dic:
                pos = dic[i + 1]
                # 求解出各个集合的交集
                # 可以分配为两个家庭的情况
                if len(pos) <= 2 and len(list(set(pos) & set(pos1 + pos3))) == 0:
                    res += 2
                # 2345/6789的情况
                elif len(list(set(pos) & set(pos1))) == 0 or len(list(set(pos) & set(pos3))) == 0:
                    res += 1
                # 6789的情况
                elif len(list(set(pos) & set(pos2))) == 0:
                    res += 1
            # 当没有当前的行没有被预约的时候那么结果肯定是加2的
        return res + 2 * (n - len(dic))

位运算:

import collections
from typing import List


class Solution:
    def maxNumberOfFamilies(self, n: int, reservedSeats: List[List[int]]) -> int:
        res = 0
        dic = collections.defaultdict(int)
        # 对应2345/4567/6789三种情况
        left, middle, right = 0b11110000, 0b11000011, 0b00001111
        for cur in reservedSeats:
            # 被预约位置在2-9的范围内才对结果是有影响的
            if 2 <= cur[1] <= 9:
                dic[cur[0]] |= (1 << (cur[1] - 2))
        # 当字典中的值与上面的三个数字异或如果等于上面三个数字说明可以分配一个家庭
        for value in dic.values():
            if left | value == left or middle | value == middle or right | value == right:
                res += 1
        # 还需要累加那些一行中没有被预约的情况, 这些情况是可以将座位分配给两个家庭的
        return res + (n - len(dic)) * 2

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值