
题目考点
这道题主要考的是这几个点:
- 输入处理
第一行读入每组人数K,第二行读入一串房间号。 - 列表遍历
需要把所有房间号一个个看过去。 - 统计次数
题目的核心其实是:找出那个只出现 1 次的房间号。 - 字典或集合思想
最朴素的方法是用字典统计每个房间号出现了几次。
审题
我们先把题目真正说的事情翻译成人话。
酒店里来了很多游客:
- 有一个队长,单独住一个房间,所以这个房间号只出现 1 次
- 其他人都是家庭出行,每个家庭有
K个人,所以同一个家庭的房间号会出现 K 次
现在给你:
- 一个整数
K - 一串乱序的房间号列表
要求你输出:
- 队长的房间号
输入是什么
第一行:
K
表示每个家庭有多少人。
第二行:
房间号1 房间号2 房间号3 ...
是一整串房间号。
输出是什么
输出那个只出现一次的房间号。
题目真正要我们判断什么
本质就是一句话:
谁的出现次数是 1?
容易忽略的点
这里有一个很重要的审题点:
题目没有给你“有几个家庭”,也没有给你“有多少游客”,但这并不影响做题。
因为我们只需要把第二行所有房间号读进来,然后统计出现次数即可。
也就是说,这题的重点不是总人数,而是:
房间号出现的规律:别人出现 K 次,队长出现 1 次。
思路提示
先不要急着写代码,先想清楚方向。
你可以这样想:
第一步:把所有房间号读进来
比如样例里会得到一个列表,里面很多数字重复出现。
第二步:统计每个房间号出现了多少次
例如:
- 1 出现了 5 次
- 2 出现了 5 次
- 3 出现了 5 次
- …
- 8 出现了 1 次
第三步:找出出现次数为 1 的那个房间号
这就是答案。
所以,这题最直接的思路就是:
先统计,再找唯一。
完整设计思路
这题对初学者最稳的做法,就是用字典计数。
第 1 步:读入 K
k = int(input())
这里只是把每个家庭的人数读进来。
第 2 步:读入第二行房间号
rooms = list(map(int, input().split()))
这句你可以这样理解:
input():读入整行字符串split():按空格切开map(int, ...):把每一项从字符串变成整数list(...):最终变成一个整数列表
第 3 步:用字典统计次数
准备一个空字典:
count = {}
然后遍历每个房间号:
- 如果这个房间号之前没出现过,就先记成 1
- 如果已经出现过,就在原来的次数上加 1
第 4 步:再遍历字典,找次数等于 1 的房间号
谁的次数是 1,谁就是队长房间。
代码实现
下面给你一个最朴素、最适合初学者理解的版本。
if __name__ == '__main__':
k = int(input())
rooms = list(map(int, input().split()))
count = {}
for room in rooms:
if room in count:
count[room] += 1
else:
count[room] = 1
for room in count:
if count[room] == 1:
print(room)
break
运行演示
我们用题目里的样例来模拟一下:
K = 5
rooms = [1, 2, 3, 6, 5, 4, 4, 2, 5, 3, 6, 1, 6, 5, 3, 2, 4, 1, 2, 5, 1, 4, 3, 6, 8, 4, 3, 1, 5, 6, 2]
统计过程大意
程序会不断更新字典:
- 看到
1,记为1: 1 - 看到
2,记为2: 1 - 看到
3,记为3: 1 - …
- 后面重复遇到,就不断加 1
最终统计结果会类似于:
{
1: 5,
2: 5,
3: 5,
4: 5,
5: 5,
6: 5,
8: 1
}
这时一看就知道:
8出现了 1 次- 所以答案就是
8
输出:
8
另一种更“题目风格”的做法
这道题其实常常被放在 set 这一章,所以还有一种更巧的写法。
为什么可以用集合
集合 set(rooms) 会自动去重。
比如原列表里:
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 8]
去重后变成:
{1, 2, 8}
如果把去重后的和乘以 K,再减去原列表总和,就能把那些重复的家庭房间抵消掉,最后只剩队长房间。
公式是:
( sum(set(rooms)) * k - sum(rooms) ) // (k - 1)
代码写法
if __name__ == '__main__':
k = int(input())
rooms = list(map(int, input().split()))
answer = (sum(set(rooms)) * k - sum(rooms)) // (k - 1)
print(answer)
这两种写法怎么选
对于初学者,我建议你先掌握第一种:
- 字典计数法更直观
- 更符合“审题 -> 统计 -> 找唯一”的思路
- 以后遇到类似题也更容易迁移
第二种集合公式法可以当作进阶补充。
方法总结
这类题你以后可以这样识别:
题目特征
如果题目出现这种描述:
- 大多数元素都重复固定次数
- 只有一个元素出现次数不同
- 让你找那个特殊值
你脑子里可以立刻想到两条路线:
路线 1:统计次数
最稳,最好理解。
步骤是:
- 读入数据
- 统计每个值出现次数
- 找特殊次数的那个值
路线 2:利用数学规律或集合规律
适合题目重复规律非常整齐时使用,比如这题里“其余都出现 K 次,只有一个出现 1 次”。
易错点回顾
1. 误以为还需要输入总人数
不需要。
因为第二行已经直接把所有房间号给你了。
2. 只想着排序,不想着“统计”
排序也能做,但这题最自然的想法其实是统计次数。
3. map(int, input().split()) 没转成列表就直接反复用
虽然这题里很多时候不转列表也能遍历一次,但初学阶段建议直接写:
rooms = list(map(int, input().split()))
更稳,也更容易理解。
4. 用集合后误以为能直接找到答案
集合只能去重,不能直接告诉你谁是只出现一次的。
必须结合总和规律,或者结合计数。
本节小结
这道题最核心的不是酒店故事,而是把它翻译成程序问题:
在一堆数字中,找出那个只出现一次的数字。
对初学者来说,最好先建立这样的做题习惯:
- 先看输入输出
- 再看数字之间的规律
- 把故事题翻译成“统计题”
这才是这道题真正训练你的地方。
练习
下面给你一道同类型的小练习,先不要急着看答案,自己试着按今天的思路做。
题目
一个整数 K = 3,表示每个家庭有 3 个人。
现在给出房间号列表:
10 20 10 30 20 10 20
已知除了队长房间外,其余房间号都出现 3 次。
请找出队长房间号。
提示
先不要想公式,先用最稳的方法:
- 统计每个房间号出现了几次
- 找出出现次数为 1 的那个房间号
集合公式法到底为什么成立?
你前面没看懂,这很正常。
因为这个方法虽然代码短,但它背后的逻辑其实比“字典计数法”更绕一点。
所以这一节我们不急着看代码,先专门把这个公式拆开讲透:
(sum(set(rooms)) * k - sum(rooms)) // (k - 1)
你只要把这句真正看懂,以后再遇到这种“多数重复 K 次,只有一个特殊值只出现 1 次”的题,就会很顺。
先说结论:这个公式是在“消掉重复项”
这道题里有一个很特别的规律:
- 普通家庭房间号,都会出现
K次 - 只有队长房间号,只出现
1次
集合公式法的核心思路就是:
想办法把那些“重复 K 次”的房间号抵消掉,最后只剩队长房间号。
它不是在“数次数”,而是在“利用总和做抵消”。
第一步:先理解 set(rooms) 到底做了什么
假设有这样一组房间号:
rooms = [1, 1, 1, 2, 2, 2, 5]
这里假设:
K = 3- 房间
1出现了 3 次 - 房间
2出现了 3 次 - 房间
5只出现了 1 次,它就是队长房间
原列表的和
sum(rooms) = 1+1+1+2+2+2+5 = 14
去重后的集合
set(rooms) = {1, 2, 5}
集合会自动去掉重复项,只保留每个房间号的一份。
所以:
sum(set(rooms)) = 1+2+5 = 8
第二步:为什么要乘以 K
这一步最关键。
我们把去重后的总和再乘以 K:
sum(set(rooms)) * k = 8 * 3 = 24
也就是:
(1 + 2 + 5) * 3 = 1*3 + 2*3 + 5*3
变成:
3 + 6 + 15 = 24
你可以把它理解成:
- 原本集合里每个房间号只有 1 份
- 乘上
K之后,相当于“人为把每个房间号都补成 K 份”
对于普通家庭房间号来说,这正好和原列表一样:
- 房间
1本来就是 3 次 - 房间
2本来就是 3 次
但是对于队长房间 5 来说:
- 原列表里它只有 1 次
- 乘上
K后,相当于把它也当成了 3 次
这就多算了。
第三步:为什么再减去 sum(rooms)
现在我们来看:
sum(set(rooms)) * k - sum(rooms)
代入上面的数:
24 - 14 = 10
为什么会得到 10?
我们展开来看。
sum(set(rooms)) * k 里包含的内容
1*3 + 2*3 + 5*3
sum(rooms) 里包含的内容
1*3 + 2*3 + 5*1
两者相减:
(1*3 + 2*3 + 5*3) - (1*3 + 2*3 + 5*1)
普通家庭房间会被完全抵消:
1*3 - 1*3 = 0
2*3 - 2*3 = 0
只剩队长房间:
5*3 - 5*1 = 5*(3-1) = 10
所以最后得到的不是队长房间号本身,而是:
队长房间号 * (K - 1)
这里就是:
5 * (3 - 1) = 10
第四步:为什么最后还要除以 (K - 1)
因为上一步剩下的是:
队长房间号 * (K - 1)
所以只要再除以 (K - 1),就能把真正的队长房间号还原出来:
10 // 2 = 5
所以最终公式就是:
(sum(set(rooms)) * k - sum(rooms)) // (k - 1)
用代数方式再看一遍
如果你想把它彻底吃透,可以看这个“字母版”。
假设:
- 普通家庭房间号有
a, b, c - 队长房间号是
x - 每个家庭人数是
K
那么原列表总和就是:
sum(rooms) = a*K + b*K + c*K + x
因为 x 只出现 1 次。
而去重后的集合总和是:
sum(set(rooms)) = a + b + c + x
再乘以 K:
sum(set(rooms)) * K = a*K + b*K + c*K + x*K
现在两者相减:
(sum(set(rooms)) * K) - sum(rooms)
= (a*K + b*K + c*K + x*K) - (a*K + b*K + c*K + x)
= x*K - x
= x*(K - 1)
最后除以 (K - 1):
x*(K - 1) // (K - 1) = x
这就得到队长房间号了。
为什么这个方法成立的前提很严格
这个公式不是随便什么题都能用,它成立必须满足两个条件:
条件 1:除了一个特殊值,其他值都必须出现完全相同的 K 次
比如都出现 5 次,或者都出现 3 次。
条件 2:特殊值只出现 1 次
如果题目改成:
- 有两个特殊值
- 或者特殊值出现 2 次
- 或者其他家庭出现次数不统一
那这个公式就不一定能用了。
所以你要记住:
集合公式法是“利用固定重复次数做抵消”的技巧题方法,不是通用统计法。
为什么字典计数法更好理解,而集合公式法更像技巧
这两种方法的本质区别是:
字典计数法
思路非常直接:
- 谁出现几次,我就老老实实记下来
- 最后找出现 1 次的
它更适合初学者,因为它是“看得见过程”的。
集合公式法
思路更像数学技巧:
- 先去重
- 再补成
K份 - 再减去原总和
- 用抵消得到队长房间
它代码短,但不如字典法直观。
所以你的感觉完全正常:
这不是你不会,是这个方法本来就比字典法更绕。
代入题目样例看一次
题目样例中:
K = 5
房间号中:
- 1, 2, 3, 4, 5, 6 都出现了 5 次
- 8 只出现了 1 次
去重后的集合
{1, 2, 3, 4, 5, 6, 8}
它们的和是:
1 + 2 + 3 + 4 + 5 + 6 + 8 = 29
乘以 K = 5:
29 * 5 = 145
原列表总和呢?
因为 1 到 6 各出现 5 次,8 出现 1 次:
(1+2+3+4+5+6)*5 + 8
= 21*5 + 8
= 105 + 8
= 113
相减:
145 - 113 = 32
这个 32 其实就是:
8 * (5 - 1) = 8 * 4 = 32
最后除以 4:
32 // 4 = 8
答案就是 8。
一句话记忆这个公式
你可以这样记:
去重后的和,相当于每个房间只留一份;乘 K,是把每个房间都补成 K 份;减去原列表后,普通家庭正好抵消,只剩队长多补出来的 K-1 份。
这句话有点长,但它就是这个公式的本质。
代码再看一遍,就不神秘了
if __name__ == '__main__':
k = int(input())
rooms = list(map(int, input().split()))
answer = (sum(set(rooms)) * k - sum(rooms)) // (k - 1)
print(answer)
现在你应该能把每一部分对上了:
set(rooms):去重sum(set(rooms)):每种房间号只算一次* k:假装每个房间都出现K次- sum(rooms):把原来的真实总和减掉// (k - 1):还原出队长房间号
你以后怎么判断能不能用这个公式
看到题目时,你先问自己三个问题:
1. 其他数是不是都重复固定的 K 次?
如果是,继续。
2. 是不是只有一个特殊数出现 1 次?
如果是,继续。
3. 我是不是只需要找这个唯一的特殊值?
如果是,那就可以考虑这个公式。
否则,老老实实用字典计数。
本节小结
集合公式法的本质不是“集合很神奇”,而是:
用去重后的总和和原总和做差,把重复项全部抵消掉。
最后只会剩下队长多补出来的那几份,所以再除以 (K - 1) 就可以了。
如果你现在已经能理解下面这句话,就说明你真的看懂了:
普通家庭在“补成 K 份”和“原本就是 K 份”之间会互相抵消,只有队长原本只有 1 份,所以会留下 (K - 1) 份。
练习
你可以自己手算这道小题,不要急着写代码。
题目
K = 3
rooms = [7, 3, 7, 7, 5, 3, 3]
请你用集合公式法算出队长房间号。
提示
按这个顺序手算:
- 先算
set(rooms) - 再算
sum(set(rooms)) - 再乘以
K - 再减去
sum(rooms) - 最后除以
K - 1
补充说明:为什么这里能用 set,而不能直接用 list
这个问题非常关键。
因为如果这一点不弄明白,集合公式法就会一直像“硬背公式”。
这节我们专门讲清楚:
为什么公式里必须写 sum(set(rooms)),而不能直接写 sum(rooms),也不能把 list 放到那个位置替代掉。
先说结论
在这道题里:
list保存的是原始完整数据set保存的是去重后的数据
而集合公式法之所以成立,核心前提就是:
我们必须先拿到“每个房间号只保留一份”的结果。
这个工作,set 能自动完成,list 不能。
所以不是说 list 完全不能用,而是说:
- 原始房间号当然要先用
list读进来 - 但在公式的“去重”这一步,必须用
set
先区分:list 和 set 到底有什么本质区别
list 的特点
列表会保留所有元素,包括重复项。
例如:
rooms = [1, 1, 1, 2, 2, 2, 5]
这个列表里:
1会保留 3 次2会保留 3 次5保留 1 次
列表不会帮你去重。
set 的特点
集合只保留不重复的元素。
set(rooms) = {1, 2, 5}
它自动把重复项去掉了。
这正是这道题最需要的性质。
这道题为什么一定需要“去重后的结果”
我们再回到题目的本质:
- 普通家庭房间号都出现
K次 - 队长房间号只出现 1 次
集合公式法其实是在做这样一件事:
第一步:先让“每个房间号都只保留一份”
比如:
rooms = [1, 1, 1, 2, 2, 2, 5]
去重后得到:
{1, 2, 5}
这时候每个房间号都只有一份了。
第二步:再把这些“只保留一份”的房间号统一乘上 K
(1 + 2 + 5) * 3
这一步的意思其实是:
假装每个房间号都应该出现 3 次。
于是:
- 房间
1被补成 3 次 - 房间
2被补成 3 次 - 房间
5也被补成 3 次
然后再减去原始列表总和,普通家庭就抵消掉,最后只剩队长多补出来的部分。
注意这里最关键的一点:
如果你一开始没有“去重”,那你根本就不是在“每个房间只保留一份”的基础上乘 K,整个公式就失效了。
为什么不能直接用 list
这是最核心的问题。
假设我们错误地把公式里的 set(rooms) 换成 rooms 本身,也就是直接写成:
(sum(rooms) * k - sum(rooms)) // (k - 1)
我们看看会发生什么。
用具体例子看错误在哪里
还是用这个例子:
k = 3
rooms = [1, 1, 1, 2, 2, 2, 5]
正确做法
先去重:
set(rooms) = {1, 2, 5}
sum(set(rooms)) = 8
然后:
(8 * 3 - 14) // 2
= (24 - 14) // 2
= 10 // 2
= 5
得到正确答案 5。
错误做法:把 set(rooms) 换成 rooms
因为:
sum(rooms) = 14
那么错误公式会变成:
(14 * 3 - 14) // 2
= (42 - 14) // 2
= 28 // 2
= 14
结果变成了 14,这根本不是队长房间号。
为什么会错得这么离谱
因为 list 里有重复项。
你写:
sum(rooms)
得到的是:
1+1+1+2+2+2+5
这是“真实总和”。
但集合公式法前半部分需要的是:
每个房间号只算一次之后的总和
也就是:
1+2+5
如果你不用 set 去重,而还是拿原列表去乘 K,本质上你是在把“已经重复过的数据”又重复放大了一次。
所以公式当然就坏了。
你可以把它理解成两种完全不同的数据
同样都是房间号,它们在这题里其实扮演了两种角色。
角色 1:原始完整名单
[1, 1, 1, 2, 2, 2, 5]
这个列表表示真实入住情况。
它告诉你:
- 房间 1 有 3 个人
- 房间 2 有 3 个人
- 房间 5 有 1 个人
这就是原始数据。
所以:
sum(rooms)
表示的是“真实总和”。
角色 2:房间号种类名单
{1, 2, 5}
这个集合不是在表示“每个人住哪间房”,而是在表示:
一共有哪些不同的房间号。
所以:
sum(set(rooms))
表示的是“每种房间号只取一次后的总和”。
而集合公式法正是需要这种“每种只取一次”的数据。
为什么 list 不能自动完成“去重”这件事
列表本身的设计目标不是去重,而是按顺序保存所有元素。
例如:
rooms = [1, 1, 1, 2, 2, 2, 5]
列表保留的是:
- 元素顺序
- 重复次数
- 完整原始记录
而集合 set 的设计目标正好相反:
- 不关心顺序
- 自动去重
- 只关心“有哪些不同元素”
所以这题里:
- 你需要完整数据时,用
list - 你需要“每个房间号只留一份”时,用
set
可以这么记:list 适合“记录全部”,set 适合“保留种类”
这句话很适合初学阶段记忆。
list
适合保存所有数据,不会丢掉重复项。
set
适合提取“不重复元素”。
这道题的公式法恰好同时需要两种信息:
- 原始完整数据的总和
用sum(rooms) - 去重后每种房间号的总和
用sum(set(rooms))
所以这题不是“set 替代 list”,而是:
先有 list,再从 list 变出 set。
一个更直观的比喻
你可以把这道题想成“学生名单”。
原始名单:
['张三', '张三', '张三', '李四', '李四', '李四', '王五']
这是完整签到表,谁来几次都记着。
而集合去重后:
{'张三', '李四', '王五'}
这变成了“有哪些不同的人”。
如果你要统计“出现总次数”,用原始名单。
如果你要统计“有几种不同的人”,用集合。
这题里的房间号也是一样。
这题里 list 到底还能不能用
当然能用,而且必须用。
因为输入读进来以后,最自然的形式就是列表:
rooms = list(map(int, input().split()))
这个 rooms 本身就是列表。
然后你可以根据需要做两件事:
用列表本身
sum(rooms)
得到真实总和。
在列表基础上转成集合
sum(set(rooms))
得到去重后的总和。
所以准确地说,不是“不能用 list”,而是:
不能用 list 去代替公式里本来应该承担“去重”任务的 set。
一眼判断:什么时候必须用 set
当你看到题目中出现这些需求时,就要想到 set:
- 去重
- 只保留不同元素
- 判断有哪些不同值
- 利用“不重复元素”做运算
而这道题的公式法,本质上就是:
先拿到不重复的房间号,再进行数学抵消。
所以必须用 set。
和字典计数法再对比一下
你会发现:
字典计数法
关心的是:
- 每个房间号出现几次
所以它保留的是“次数信息”。
集合公式法
关心的是:
- 哪些房间号是不同的
- 去重后的总和是多少
所以它保留的是“去重结果”。
这也是为什么两种方法都能做题,但用到的数据结构不同。
本节小结
这题中:
list用来保存完整房间号列表set用来得到“每个房间号只保留一份”的结果
集合公式法之所以成立,关键就在于“去重”这一步。
而 list 不会自动去重,set 会。
所以不是说 list 没用,而是说:
在公式里那个位置,我们需要的是“去重后的房间号集合”,只有 set 能直接完成这个任务。
你可以把这句话记住:
sum(rooms) 是真实总和,sum(set(rooms)) 是去重后的种类总和。集合公式法同时需要这两种总和。
练习
你可以自己判断下面这两个表达式分别代表什么含义。
题目
rooms = [4, 4, 4, 9]
请分别说明:
sum(rooms)
sum(set(rooms))
它们各自表示什么?
提示
你不要只算结果,还要说出它们的“意义”:
- 一个表示完整列表总和
- 一个表示去重后总和



