一、招聘策略
某次招聘活动中,共有 deptNum 个部门在招人,每个部门都设定了自己的:招聘目标人数、机考和技面的最低分要求。共有 candidateNum 个应聘者,每个应聘者都有两项能力值:机考分数和技面分数。
招聘规则如下:
- 选人规则:首先达到部门的门槛要求,即机考和技面分数均大于等于对应最低分要求;然后选人:优先录取技面分数较高的;若技面分数相同,则录取机考分数较高的;若机考分数也相同,则录取应聘者编号较小的。
- 先进行常规录取,此阶段采用轮询录取方式,以完成招聘目标:
- 每一轮按部门编号从小到大轮询,部门遵照选人规则录取一人,若无符合要求者则放弃;若已招满(或已无人可录),不再选人;
- 不断轮询,直至所有部门都完成招聘目标(但不能超过)、或未招满但已无人可录。
- 在常规录取阶段结束后,各部门均尝试进行一次补录,但最多补录一人(即可能比招聘目标多一人):
- 按部门编号从小到大的顺序进行补录;
- 如果还有应聘者与该部门常规录取的最后一名应聘者能力相同(即机考和技面分数均相同),则部门参照选人规则补录一人即结束招聘(即使未成功补录,也结束招聘)。
现给出部门的招聘需求deptDemands,以及应聘者的信息 candidateAbilities。请按部门编号从小到大依次输出所录取人员,部门内所录取人员按录取顺序输出(若无录取人员,输出空序列 [])。
输入
第一行的整数表示招聘部门个数 deptNum,1 <= deptNum <= 10
随后 deptNum 行依次表示每个部门的要求,记录于 deptDemands 数组中,deptDemands[i] 表示编号为 i 的部门的目标人数 机考最低分 技面最低分
,目标人数范围 [1, 200],最低分范围 [100, 150]
接下来一行的整数表示应聘者数量 candidateNum
,1 <= candidateNum <= 10000
随后 candidateNum 行依次表示每个应聘者的信息,记录于数组 candidateAbilities 中,candidateAbilities[j] 表示编号为 j 的应聘者的机考分数 技面分数
,机考和技面分数取值范围 [100, 200]
输出
按部门编号从小到大,每行输出一个部门所录取人员的编号;若没有录取人员,输出空序列 [] 。
样例
输入样例 1
2 2 130 120 1 150 150 6 150 100 160 190 150 200 200 190 160 190 160 190
输出样例 1
[2 1 4] [3]
提示样例 1
招聘录取过程解释如下:
- 第一轮:按部门编号从小到大,部门0 先选人,按选人规则优先录取了应聘者2 。然后部门1选人,优先录取了应聘者 3 ,部门1招满、退出轮询。
- 第二轮:部门0 选人,应聘者1、4、5 的能力值都相同,录取编号小的应聘者1 。
两轮后部门0 和 部门1 都达到人数目标,完成常规录取。
- 补录:部门0 先选人,应聘者 4 和 5 与此前最后录取的应聘者1 的能力值相同,按规则补录一人(应聘者4)后结束招聘。 然后部门1 补录,没有人与此前最后录取的应聘者3 具备相同的能力值。
招聘结束后:部门0依次录取人员为 2 1 4,部门1依次录取人员为 3 (应聘者0 和 应聘者5 未被任何部门录取)
from typing import List
class DeptDemand:
def __init__(self, dept_id, employ_num, progm_thd, tech_thd):
self.dept_id = dept_id
self.employ_num = employ_num
self.progm_thd = progm_thd
self.tech_thd = tech_thd
# 记录录取人员
self.employ_list = []
class Candidate:
def __init__(self, candidate_id, progm_score, tech_score):
self.candidate_id = candidate_id
self.progm_score = progm_score # 机考分数
self.tech_score = tech_score # 面试分数
class Solution:
def __init__(self):
self.finish = True
self.temp = -1
def recruitment_result(self, dept_demands: List[DeptDemand],
candidate_abilities: List[Candidate]) -> List[List[int]]:
candidate_abilities.sort(key=lambda x: (x.tech_score, x.progm_score, -x.candidate_id), reverse=True)
# 招聘池不再变化结束
while len(candidate_abilities) != self.temp:
self.temp = len(candidate_abilities)
for dept_demand in dept_demands:
if len(dept_demand.employ_list) < dept_demand.employ_num: # 部门没招满
for num, candidate in enumerate(candidate_abilities):
if candidate.progm_score >= dept_demand.progm_thd and candidate.tech_score >= dept_demand.tech_thd:
dept_demand.employ_list.append(candidate_abilities.pop(num))
break
# 补录环节
for dept_demand in dept_demands:
for num, candidate in enumerate(candidate_abilities):
if dept_demand.employ_list:
last = dept_demand.employ_list[-1]
if candidate.progm_score == last.progm_score and candidate.tech_score == last.tech_score:
dept_demand.employ_list.append(candidate_abilities.pop(num))
break
else:
continue
return [[y.candidate_id for y in x.employ_list] for x in dept_demands]
- candidate_abilities.sort(key=lambda x: (x.tech_score, x.progm_score, -x.candidate_id), reverse=True) 记一下,list的排序,
while len(candidate_abilities) != self.temp
表示在candidate_abilities
的长度发生变化时继续循环。只有当candidate_abilities
的长度不再变化时,循环才会终止。- dept_demand.employ_list 遍历,可以直接.employ_list
-
return [[y.candidate_id for y in x.employ_list] for x in dept_demands]
这是一个列表推导式,用于构建返回结果。
外层 for x in dept_demands
遍历每个部门。
内层 [y.candidate_id for y in x.employ_list]
遍历每个部门的 employ_list
,提取每个被录取候选人的 candidate_id
,并生成一个包含这些 ID 的列表。
- 当
break
语句被执行时,它会退出当前的内层for
循环(遍历candidate_abilities
的循环)。然而,退出这个内层循环后,程序会继续执行外层的for
循环的下一次迭代,即检查下一个dept_demand
。但需要注意的是,虽然程序会继续检查下一个dept_demand
,但是由于外层的while
循环会重新进行检查和调整,最终还是会确保每个部门尽量满足其需求。
先满足第一个部门的招第一个人,在满足第二个部门招第一个人,再招第一个部门的剩下的人
- 这道题比较特殊,函数的入参是一个list【类】,遍历列表,列表的其中一项(比如dept_demand)又可以打点引用这个类里面的参数
二、SQL解析执行
请设计一个简易数据库系统,支持三个命令:
CREATE(int tableId, int colNum, string keys)
:创建一个表,其id为tableId;如果该表已经存在,则不做任何处理。- colNum 为该表列的数量,列名由单字母表示,并从字母 a 开始、按 a-z 顺序编号,例如colNum=3,表示有三列,列名分别为字母 a b c;
- keys 表示该表的主键(指的是一列或多列的组合,其值能唯一地标识表中的每一行),keys中每个字符代表一列,如 keys=
ba
表示主键为b列和a列的组合。
-
INSERT(int tableId, int[] values)
:添加一条记录:- values 每个元素按顺序一一对应每列的值。
- 若主键冲突,则不做任何处理。
-
SELECT(int tableId, string[] conditions)
:根据条件查询记录,并按主键升序输出结果集:- conditions 每个元素代表一个条件,格式如
a=25
,条件仅为等于(不存在大于、小于等情况);conditions 各元素间仅为 and 关系。 - 主键升序:按keys中列出现的顺序依次进行排序,每列按值大小升序输出。如keys=
ba
,先按b列升序排序,若b列值相同则进一步按a列升序排序。
- conditions 每个元素代表一个条件,格式如
现给出一批命令语句,请按输入顺序解析与执行,并输出每个SELECT 命令的结果集(CREATE 和 INSERT 命令无输出)
输入
首行为整数 num, 表示其后输入的命令行数,取值范围[1,30000]。
之后每行为一条命令,3种命令的输入格式分别为:
- CREATE 命令,格式为:
CREATE:tableId,colNum,keys
- tableId 取值范围[1,10000]。
- colNum 取值范围[1,26]。
- keys 为仅由小写字母组成的字符串,每个字符都不重复且在列名范围内,长度范围[1,26]。
- INSERT 命令,格式为:
INSERT:tableId,value1 value2 ...
- value 的取值范围 [0,10000]
- SELECT 命令,格式为:
SELECT:tableId,condition1 and condition2 ...
- 每个condition 的格式如
a=25
- 查询条件有若干个,小于等于 colNum,且不重复。
- 每个condition 的格式如
用例保证 INSERT 和 SELECT命令中tableId所代表的表是已经创建的
每种命令最多10000条命令
输出
逐个输出每个 SELECT 命令的结果集,每行输出表中所有列的值、且按列的顺序输出。
如果某个SELECT命令的查询结果为空,此SELECT输出empty
。
注:CREATE 和 INSERT 的执行结果不需要输出
样例
输入样例 1
5 CREATE:1,3,a INSERT:1,2 3 7 INSERT:1,4 5 6 INSERT:1,3 4 6 SELECT:1,b=5 and c=6
输出样例 1
4 5 6
提示样例 1
CREATE:1,3,a 创建一个id为1的数据库表,有3列(列名分别为a,b,c),a列为主键
INSERT:1,2 3 7 向数据库表1插入一条记录,其值分别为 2 3 7 (即a=2,b=3,c=7)
INSERT:1,4 5 6 向数据库表1插入一条记录,其值分别为 4 5 6
INSERT:1,3 4 6 向数据库表1插入一条记录,其值分别为 3 4 6
SELECT:1,b=5 and c=6 查询数据库表1的内容,条件是b=5并且c=6,符合要求的记录只有4 5 6 这一条
from typing import List
class Solution:
def init(self):
self.db = dict()
def create(self, table_id: int, col_num: int, keys: str) -> None:
"""在此添加你的代码"""
if table_id in self.db:
return
self.db[table_id] = {
"keys":[ord(c)-ord("a") for c in keys],
"save_vlaues":[]
}
def insert(self, table_id: int, values: List[int]) -> None:
"""在此添加你的代码"""
for v in self.db[table_id]["save_vlaues"]:
flag = True
for k in self.db[table_id]["keys"]:
if v[k] != values[k]:
flag = False
break
if flag:
return
self.db[table_id]["save_vlaues"].append(values)
def select(self, table_id: int, conditions: List[str]) -> List[int]:
"""在此添加你的代码"""
check = [(ord(c.split("=")[0])-ord("a"), int(c.split("=")[1])) for c in conditions]
res = []
for v in self.db[table_id]["save_vlaues"]:
flag = True
for i, x in check:
if v[i] != x:
flag = False
break
if flag:
res.append(v)
return sorted(res, key=lambda x:[x[i] for i in self.db[table_id]["keys"]])
if __name__ == "__main__":
num = int(input().strip())
function = Solution()
for _ in range(num):
com, value = input().strip().split(":")
if com == "CREATE":
table_id, col_num, keys = value.split(",")
function.create(int(table_id), int(col_num), keys)
elif com == "INSERT":
table_id, values = value.split(",")
values = list(map(int, values.split()))
function.insert(int(table_id), values)
elif com == "SELECT":
table_id, conditions = value.split(",")
conditions = conditions.split(" and ")
results = function.select(int(table_id), conditions)
if not results:
print('empty')
else:
for result in results:
print(str.join(" ", map(str, result)))
- 在一些情况下,可以使用
ord()
函数将字母转换为索引。例如,将小写字母 'a' 转换为 0,将 'b' 转换为 1:
index_a = ord('a') - ord('a') # 0
index_b = ord('b') - ord('a') # 1
index_c = ord('c') - ord('a') # 2
- insert函数
- 遍历
self.db[table_id]["save_vlaues"]
中已保存的所有值(即表中的所有行)。对于每一行,初始化flag
为True
,表示假设当前行的键值组合与插入的值匹配。 - 遍历
self.db[table_id]["keys"]
中的每一个键:- 比较当前行
v
中键k
对应的值与插入的值values
中键k
对应的值。 - 如果发现任意一个键值不匹配,将
flag
设为False
并跳出内层循环。 - 如果
flag
仍然为True
,说明当前行的所有键值都与插入的值匹配。这意味着表中已经存在一行具有相同键值组合的记录,此时直接返回,不插入新的值。
- 比较当前行
- 遍历
这样做的目的是为了避免在表中插入重复的键值组合,从而保持键值组合的唯一性。
假设我们有一个表,键是 a
和 b
,已有的数据如下:
[ [1, 2, 3], [4, 5, 6] ]
如果我们尝试插入 [1, 2, 7]
,由于键值组合 a=1, b=2
已经存在,插入操作将会被跳过。如果我们尝试插入 [4, 5, 8]
,同理,由于键值组合 a=4, b=5
已经存在,插入操作也会被跳过。
这样可以保证表中的键值组合是唯一的。
三、简易的文件系统
模拟设计实现一套简易的文件系统,支持以下操作:
FileSystem()
— 系统初始化,当前目录在根目录/
,根目录下为空。makeDir(string dirName)
— 在当前目录下创建一个新的目录,若目录名dirName
和当前目录下的文件名或目录名重复,则创建失败,返回 false; 否则创建成功,返回 true。createFile(string fileName)
— 在当前目录下创建一个新的文件。若文件名fileName
和当前目录下的文件名或目录名重复,则创建失败,返回 false; 否则创建成功,返回 true。-
changeDir(string pathName)
— 切换当前目录到pathName
。- 首先,若 pathName 为
""
,当前目录保持不变,返回 true; - 其次,若目录存在则切换成功,并返回 true;否则,直接返回 false 。
注:pathName 以绝对路径
/xx/yy/zz
或者相对路径xx/yy/zz
给出。
- 首先,若 pathName 为
-
remove(string name)
— 删除当前目录下名称为name
的文件或目录。- 若存在则删除成功,并返回 true;否则,直接返回 false 。
- 注:删除目录时,除删除目录本身外,还需删除该目录下的所有子目录和文件。
listDir()
— 返回当前目录下的目录和文件。目录在前、文件在后,各自按字典序升序。- 注:返回结果不含子目录下的内容
- 输入
- 每行表示一个函数调用,初始化函数仅首行调用一次,累计函数调用不超过 1000 次。
-
fileName、dirName、name 仅含小写字母,长度范围皆为 [1, 100]
pathName 仅含小写字母和/
, 0 <= pathName.length < 1000,注:不包含..
或者.
注:pathName 不为 “” 时为合法目录名,末尾可能带一个
/
。输出
答题时按照函数/方法原型中的要求(含返回值)进行实现,输出由框架完成(其中首行固定输出 null)
样例
输入样例 1
FileSystem() makeDir("dirc") makeDir("dirb") makeDir("dirc") listDir() changeDir("dirc/") createFile("fileb") makeDir("dirb") createFile("dirb") listDir() changeDir("/dirb/dirc")
输出样例 1
null true true false ["dirb", "dirc"] true true true false ["dirb", "fileb"] false
提示样例 1
FileSystem() // null
makeDir(“dirc”) // true
makeDir(“dirb”) // true
makeDir(“dirc”) // false,重复
listDir() // [“dirb”, “dirc”],注意:目录名末尾不要额外加 /
changeDir(“dirc/“) // true,末尾带了一个 / ,等同于目录 dirc
createFile(“fileb”) // true
makeDir(“dirb”) // true
createFile(“dirb”) // false,重复
listDir() // [“dirb”, “fileb”]
changeDir(“/dirb/dirc”) // false,目录不存在
-
from typing import List
class FileSystem:
def __init__(self):
self.root = {"name":'/',"type":'dir', "content":{}}
self.curr = self.root
def make_dir(self, dir_name: str) -> bool:
if self.curr["content"].get(dir_name):
return False
self.curr["content"][dir_name] = {"name":dir_name,"type":'dir', "content":{}}
return True
def create_file(self, file_name: str) -> bool:
if self.curr["content"].get(file_name):
return False
self.curr["content"][file_name] = {"name":file_name,"type":'file'}
return True
def change_dir(self, path_name: str) -> bool:
if path_name == '':
return True
paths = path_name.split('/')
curr = self.root if path_name.startswith('/') else self.curr
for path in paths:
if not path:
continue
if not curr["content"].get(path) or curr["content"][path]["type"] != 'dir':
return False
curr = curr["content"][path]
self.curr = curr
return True
def remove(self, name: str) -> bool:
if not self.curr["content"].get(name):
return False
del self.curr["content"][name]
return True
def list_dir(self) -> List[str]:
dirs = []
files = []
for name,value in self.curr["content"].items():
if value["type"] == 'file':
files.append(name)
elif value["type"] == 'dir':
dirs.append(name)
return sorted(dirs)+sorted(files)
字典的嵌套使用,经典题目
四、版本满足度
现约定软件版本号格式为三段x.y.z
,x段表示主版本,y段表示次要版本,z段表示修订版本,y段、z段是可选的(即可以为 x.y 或 x),每段的值范围是 [0,999]之间的整数。
软件的兼容、依赖等管理中,通常需要用到「版本范围」,一种「版本范围」的表示方法为前缀x.y.z
,说明如下:
- 前缀为
=
、>
、>=
、<
、<=
,分别表示等于、大于、大于等于、小于、小于等于给定版本。若未给定y段或z段,则表示 y=0 或 z=0 -
前缀为
~
,表示的版本范围如下:-
如果给定了次要版本,则从该版本到下一个次要版本(不包括下一个次要版本)。若未给定z段,则表示 z=0
如 ~1.9 次要版本9的下一次要版本是10,表示范围为 [1.9.0, 1.10.0) 即 [1.9.0, 1.9.999]
如 ~1.9.5 表示 [1.9.5, 1.10.0) 即 [1.9.5, 1.9.999]
如 ~1.0.0 表示 [1.0.0, 1.1.0) 即 [1.0.0, 1.0.999] -
如果未给定次要版本(即仅有主版本),则从该版本到下一个主版本(不包括下一个主版本)。未给定的y段和z段均为 0
如 ~1 表示 [1.0.0, 2.0.0) 即 [1.0.0, 1.999.999];注意它与 ~1.0.0 或 ~1.0 是不同的
-
现给出多对「版本范围」;对于每一对输入的两个范围rangeA rangeB
,如果存在交集(即存在版本同时满足两个范围)输出字符串yes
,否则输出no
输入样例 1
4 >1.0.0 <=1.0.0 <1.2.0 >1.1.999 ~0.10 =0.10.0 =99.0 ~99
输出样例 1
no no yes yes
提示样例 1
输入有四对版本范围,依次判断如下:
>1.0.0
表示版本范围为 [1.0.1,999.999.999],<=1.0.0
表示版本范围为 [0.0.0,1.0.0]。第一个范围的左值 1.0.1 大于第二个范围的右值 1.0.0,因此两个范围不存在交集<1.2.0
表示范围 [0.0.0,1.1.999];>1.1.999
的z段已经是最大值999,相当于 >=1.2.0,因此表示范围为 [1.2.0,999.999.999]。第一个范围的右值小于第二个范围的左值,因此两个范围不存在交集~0.10
给定了次要版本,表示范围为 [0.10.0, 0.11.0) 即 [0.10.0, 0.10.999],=0.10.0
表示范围 [0.10.0, 0.10.0],版本 0.10.0 同时在两个版本范围内,存在交集~99
仅有主版本(未给定次要版本),表示范围为 [99.0.0, 100.0.0) 即 [99.0.0, 99.999.999],版本 99.0.0 同时在两个版本范围内,存在交集
版本号可以使用字符串、三元组、整数等方式来表示和比较
"""
Copyright (c) Huawei Technologies Co., Ltd. 2019-2021. All rights reserved.
Description: 上机编程认证
Note: 缺省代码仅供参考,可自行决定使用、修改或删除
"""
from typing import List
import re
class Version:
def __init__(self, version: str):
self.min = 0
self.max = 999999999
matched = re.match(r'([=<>~]+)(\d+).?(\d+)?.?(\d+)?', version)
if matched:
self.op = matched.group(1)
self.major = int(matched.group(2))
self.minor = matched.group(3)
self.step = matched.group(4)
op = {
'=': self.process_eq,
'<': self.process_less,
'<=': self.process_less,
'>': self.process_great,
'>=': self.process_great,
'~': self.process_normal
}.get(self.op)
if op:
op()
else:
raise ValueError("Invalid version string format")
@staticmethod
def _convert_to_int(number):
if number is not None:
return int(number)
return 0
@staticmethod
def _convert_to_calc_number(major, minor, step):
minor = Version._convert_to_int(minor)
step = Version._convert_to_int(step)
return major * 1000000 + minor * 1000 + step
@staticmethod
def _convert_to_upper_number(major, minor, step):
if minor is None:
minor = 999
else:
minor = int(minor)
step = 999
return major * 1000000 + minor * 1000 + step
def process_eq(self):
self.min = Version._convert_to_calc_number(self.major, self.minor, self.step)
self.max = self.min
def process_less(self):
self.max = Version._convert_to_calc_number(self.major, self.minor, self.step)
if '=' not in self.op:
self.max -= 1
def process_great(self):
self.min = Version._convert_to_calc_number(self.major, self.minor, self.step)
if '=' not in self.op:
self.min += 1
def process_normal(self):
self.min = Version._convert_to_calc_number(self.major, self.minor, self.step)
self.max = Version._convert_to_upper_number(self.major, self.minor, self.step)
def have_version(self, another: 'Version') -> str:
if self.max < another.min or self.min > another.max:
return 'no'
return 'yes'
def check(num: int, range_pairs: List[List[str]]) -> List[str]:
results = []
for range_pair in range_pairs:
version1 = Version(range_pair[0])
version2 = Version(range_pair[1])
result = version1.have_version(version2)
results.append(result)
return results
if __name__ == "__main__":
num = int(input().strip())
range_pairs = [list(map(str, input().strip().split())) for _ in range(num)]
results = check(num, range_pairs)
print("\n".join(results))