小欧安排座位
小欧安排座位
OPPO 笔试真题 · 2024 秋招 · 移动端
判题请到牛客原题提交。
题目描述
小欧现在有个小朋友,同时有个座位。小欧现在要给他们安排座位,个小朋友中有一些希望自己是独特的,另一些小朋友则希望自己是不独特的。
当一个小朋友是独特的当且仅当小朋友自身的编号与座位的编号不相等,否则这个小朋友就是不独特的。
现在给你个小朋友的需求,你需要给他们安排座位,满足尽可能多的小朋友的需求。
输入格式
第一行一个整数表示小朋友的数量。
第二行输入一个长度为只包含 0 和 1 的字符串,第位若为 1 代表第个小朋友希望自己是独特的,否则他希望自己是不独特的。
输出格式
个用空格隔开的整数,第个表示第个小朋友的位置。若有多种安排方案,输出任意一个即可。
示例
示例 1
输入:
5
00110输出:
1 2 4 3 5说明:
这样安排满足了所有小朋友的需求
示例 2
输入:
2
01输出:
1 2说明:
只能满足 1 个小朋友的需求
解题思路
第一步:理解问题本质
安排座位就是一个排列 :第 个小朋友坐 号座位,
且 恰好是 到 的一个排列(每个座位只坐一个人)。
- 若 ,第 个小朋友「不独特」;
- 若 ,他是「独特」的。
每个小朋友有一个愿望:s[i] = '1' 希望独特,s[i] = '0' 希望不独特。
要求安排座位,使满足愿望的人数最多。
第二步:先照顾「希望不独特」的小朋友
对 s[i] = '0' 的小朋友,直接让他坐 号座位。这样做有两个好处:
- 他的愿望立刻满足;
- 他的座位本来就是他自己的,不会被别人需要(每个座位编号唯一)。
所以「希望不独特」的人可以全部满足,而且不占用任何额外的座位。
第三步:剩下的问题是一个「错排」
把 s[i] = '0' 的人按原位安排好之后,还剩哪些座位?
恰好是 s[i] = '1' 的那些人自己的座位 —— 数量、编号都对得上。
于是问题变成:在集合 内部,把座位重新分配,
要求每个人都不坐自己的座位(错排)。这样每个 '1' 的人都满足愿望。
第四步:错排的构造
设 里的元素按编号排好是 ,其中 。
- :让 坐 的座位、 坐 的座位、……、
坐 的座位(循环移位一位)。
每个人坐的都是别人的座位,全部满足,一个都不用牺牲。 - :只剩一个人和一个座位,他只能坐自己的座位,
这个人必然不满足。其余 个人都满足。 - :没有人希望独特,全都坐原位,全部满足。
也就是说:只有「恰好一个人希望独特」时才会牺牲一个人,其它情况全部满足。
完整代码实现
import sys
def solve():
"""希望不独特的坐原位;希望独特的在自己人内部循环移位一位"""
data = sys.stdin.read().split()
if not data:
return
n = int(data[0])
s = data[1]
ans = list(range(1, n + 1)) # 默认每个人坐自己的座位
special = [i for i in range(1, n + 1) if s[i - 1] == '1']
if len(special) >= 2:
m = len(special)
for k in range(m):
ans[special[k] - 1] = special[(k + 1) % m]
sys.stdout.write(" ".join(map(str, ans)) + "\n")
if __name__ == "__main__":
solve()示例推演
示例 1:,s = "00110"。
希望独特的是第 3、4 号小朋友,希望不独特的是第 1、2、5 号。
第一步:不独特的人坐原位
| 小朋友 | 座位 |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 5 | 5 |
第二步:3、4 号在剩下的座位 {3, 4} 里循环移位
| 小朋友 | 原本的座位 | 安排到的座位 |
|---|---|---|
| 3 | 3 | 4 |
| 4 | 4 | 3 |
第三步:合并结果
| 小朋友 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 座位 | 1 | 2 | 4 | 3 | 5 |
输出 1 2 4 3 5,五个人的愿望全部满足,与样例一致。
示例 2:,s = "01"。
希望独特的只有第 2 号一个人,。他只能坐 2 号座位,
结果是 1 2 —— 1 号满足(不独特),2 号不满足(他想要独特)。
最多只能满足 1 个人,与样例说明一致。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 枚举所有排列取最优 | 只在小数据下可行 | ||
| 构造:原位 + 循环移位 | 推荐,一遍扫描即可 |
易错点总结
1. 时构造不出错排
一个人、一个座位,他只能坐自己的。此时不需要任何特殊处理:
代码里默认「全部坐原位」,已经是对这种情况的最优安排。
不要再拿他去找别人交换 —— 别人已经坐在自己的位置上,
换过去只会破坏别人的愿望,满足人数并不会变多。
2. 循环移位要取模
最后一个人要坐第一个人的座位,写成 special[(k + 1) % m],
漏掉取模会让最后一个人坐自己的座位(愿望落空)。
3. 输出必须是 到 的排列
每个座位只能出现一次、每个座位都要有人坐。用「先全部坐原位、
再在集合内部置换」的写法天然保证了这一点。
4. 读入的字符串不要逐字符读行
第二行是长度为 的 0/1 字符串,sys.stdin.read().split() 会得到[n, s] 两个 token,直接取第二个即可。用 input().split() 按行读时
要小心行尾的空格。
5. 输出顺序按小朋友编号,不是按座位编号
第 个数字是「第 个小朋友坐哪」,不要按座位顺序输出。
扩展思考
- 题目只要求最大化「满足愿望的人数」,所以 时牺牲那一个人是最优的。
若题目改成「必须让所有希望独特的人都独特」,那就只有 时无解。 - 若把「独特」的定义改成「座位编号与自己编号的奇偶性不同」,
问题就变成纯粹的奇偶匹配,与「小欧的排列交换」是同一类构造。 - 错排的存在性有一个经典结论: 时错排总是存在(最简单地,
让所有人循环移位一位)。本题正是这个结论的直接应用。 - 「先固定能固定的,再把剩余部分变成一个更小的问题」是构造题里最常见的
拆解方式:把 个人的问题拆成「全部满足的固定点」+「一个规模更小的错排」。