关注我们,每天更新大厂最新笔试题解析
前言
文末给大家推荐一下我们的活动,针对校招笔面试全覆盖的全程真一对一的提高,参加活动前会给你安排一个全面的摸底,摸底后会针对你的情况进行分析和对于参加活动的介绍,然后你再决定是否参加!
在线做题链接:https://niumacode.com/
- 第一题(乱序数据解析):巧妙利用数据类型的排他性强特征(小写字母开头判别名称、包含小数点定位经验值),在乱序输入流中精准拦截并分类字段,打包后直接以用户编号为关键字完成升序排序重组,是考察基本功的优质签到题。
- 第二题(无限轮互评模拟):摒弃低效的步进模拟,将连续的同属性串压缩分块。通过奇偶索引交替,模拟“敌军累加”与“友军抵消”的连锁反应,最终以总长度扣除存活的“绝对安全区”长度,在 复杂度内降维算出被消耗的总量。
- 第三题(双向字符置换):紧扣“字典序优先极小化前缀”的贪心本质,借助线段树(RMQ)高效查找到最靠前且收益最大的首轮交换位置以敲定跨度步长。随后在后半段合法区间内再次贪心锁定代价最小的副交换点,从而实现“连带双重置换”约束下的全局字典序最小。
第一题
在线做题链接:https://niumacode.com/
题意
小红书数据库中有用户编号、用户名称和用户经验三个字段,其中: 用户编号为 1 到 10^9 间的整数,且唯一; 用户名称为长度不超过 10 的非空字符串,且仅由小写字母构成; 用户经验为 1 到 10^9 间的浮点数。 现在,你已经获取到了 n 条用户数据,每一条用户数据由:用户编号、用户名称、用户经验三个部分组成,但顺序是混乱的。 请按照:用户编号从小到大排序并将排序后的用户数据按照:用户编号、用户名称、用户经验的顺序输出。
输入描述
输入一个整数 n (1≤n≤10^5),表示用户数据的数量。此后 n 行,第 i 行依次输入以下三个部分表示第 i 条用户数据(不保证某个部分一定在前,即部分间的顺序是乱序的;各部分之间用空格分隔): 一个整数 xi (1≤xi≤10^9),表示用户编号; 一个长度仅由小写字母构成的字符串 si (1≤length(si)≤10),表示用户名称; 一个小数位数为两位的浮点数 ci (0≤ci≤10^9),表示用户经验。
输出描述
一共 n 行,第 i 行依次输出用户编号第 i 小的用户编号、用户名称和用户经验,用空格分隔。
示例1:
输入:
3
xhs 12 106.70
0.00 abc 11
6 xhs 666.66
输出:
6 xhs 666.66
11 abc 0.00
12 xhs 106.70
思路与代码
由于每条数据的三个字段输入顺序是随机的,但它们各自具有严格的数据类型排他性:由小写字母构成的字符串必是“用户名称”,包含小数点的浮点型格式必是“用户经验”,剩下的纯数字构成的必定是“用户编号”。在读入时,利用字符串首字母范围和是否包含 . 这两个特征,精准拦截并分类各个字段,将它们打包存入列表后,直接以用户编号为关键字进行升序排序并输出即可。
import sys
def solve():
# 快速读取所有标准输入并按空白字符分割
input_data = sys.stdin.read().split()
if not input_data:
return
n = int(input_data[0])
idx = 1
user_list = []
for _ in range(n):
user_id = 0
user_name = ""
user_exp = ""
# 每条记录由 3 个部分组成,乱序输入
for _ in range(3):
token = input_data[idx]
idx += 1
# 依靠特征区分当前字段 (token) 属于哪种数据
if'a' <= token[0] <= 'z':
user_name = token # 小写字母开头为用户名称
elif'.'in token:
user_exp = token # 包含小数点为经验值
else:
user_id = int(token) # 否则为用户编号
user_list.append((user_id, user_name, user_exp))
# 按照用户编号 user_id (即元组的第 0 个元素) 从小到大排序
user_list.sort(key=lambda user: user[0])
# 统一格式化输出
out = [f"{user[0]} {user[1]} {user[2]}"for user in user_list]
print('\n'.join(out))
if __name__ == '__main__':
solve()
第二题
在线做题链接:https://niumacode.com/
题意
现在有 n 条 Plog 在首页上排成一列,队尾在下侧,队头在上侧。用长度为 n 的 01 串 s=s1s2…sn 表示这条队列,其中: 若 si=1,则第 i 条 Plog 属于美食; 若 si=0,则第 i 条 Plog 属于旅行。 一共会进行无限轮互评操作,每一轮: 所有 Plog 的拥有者同时向队头(右侧)互评; 互评只会影响每条 Plog 右侧的第一个异属性 Plog,如果右侧没有异属性 Plog,则不会产生互评操作; 每轮所有互评动作并行计算,然后一次性将所有已经有评论的 Plog 移出,形成新队列再进入下一轮;同一条 Plog 在一轮可能收获多条评价。
显然,无限进行下去,终究会出现不再有互评发生的情况。求整个过程中共有多少条 Plog 收获评价。
输入描述
第一行输入一个整数 n (1≤n≤10^5),表示 Plog 数量。第二行输入一个长度为 n 且只由字符 0 和 1 构成的字符串 s,表示 Plog 的属性分布,其中 si 为从左向右第 i 条 Plog 的属性。
输出描述
输出一个整数,表示所有互评结束后共有多少条 Plog 收获评价。
示例1:
输入:
5
11101
输出:
2
解释: 在这个样例中:第一轮,第三条(1)评论第四条(0),第四条(0)评论第五条(1),共 2 条 Plog 收获评论;剩余前三条 Plog 拼接为 "111",此时剩下的全是美食 Plog,不再发生互评现象。
示例2:
输入:
10
1100010101
输出 8
解释:
"1100010101" → "1100" → "110" → "11"
一共有 8 条 Plog 被评论
思路与代码
首先将连续的同属性 Plog 压缩成若干个长度块,最左侧的第一个块视为“绝对安全区”,后续的块按照索引奇偶性交替扮演“异属性(敌人)”和“同属性(友军)”的角色。在从左到右遍历的过程中,将奇数索引的异属性长度不断累加,代表“挡在前面的敌人”;当遇到偶数索引的同属性块时,让它与前面累积的敌人互相消耗(同属性多则余下部分并入安全区,异属性多则同属性被全部吞噬)。最终,用 Plog 总长度减去存活在安全区的总长度,就是整个过程中发生过互评(被消耗掉)的 Plog 数量。
import sys
def solve():
# 快速读取所有标准输入并按空白字符分割
input_data = sys.stdin.read().split()
if not input_data:
return
n = int(input_data[0])
s = input_data[1]
if n <= 1:
print(0)
return
# 将连续相同的字符压缩为长度块
block_lengths = []
current_char = s[0]
current_count = 1
for i in range(1, n):
if s[i] == current_char:
current_count += 1
else:
block_lengths.append(current_count)
current_char = s[i]
current_count = 1
block_lengths.append(current_count)
# 如果只有一种属性,没有互评
if len(block_lengths) <= 1:
print(0)
return
safe_count = block_lengths[0] # 第一块必定是安全的起始块
pending_enemies = 0 # 累积的异属性数量(等待抵消的敌人)
for i in range(1, len(block_lengths)):
if i % 2 != 0:
# 奇数索引:异属性块,敌人数量增加
pending_enemies += block_lengths[i]
else:
# 偶数索引:同属性块,与累积的敌人发生抵消
if block_lengths[i] > pending_enemies:
# 友军数量大于敌人,剩余友军加入安全区,敌人清空
safe_count += block_lengths[i] - pending_enemies
pending_enemies = 0
else:
# 友军数量小于等于敌人,友军全灭,敌人数量对应减少
pending_enemies -= block_lengths[i]
# 总被评论数 = 总长度 - 最终存活的安全长度
total_commented = n - safe_count
print(total_commented)
if __name__ == '__main__':
solve()
第三题
在线做题链接:https://niumacode.com/
题意
为了提升小红书笔记标签的可读性,我们计划对标签字符串进行一次双向字符置换操作,以获得更小的字典序结果。具体地,给定一个长度为 n 的字符串 s(下标从 1 开始),你可以进行一次如下操作: 选取三个整数 (i,j,k),满足 1≤i≤j≤n,1≤i−k,j+k≤n 且 k>0;将 si 与 si−k 交换,并将 sj 与 sj+k 交换。 在所有可行操作中,找出能够使字符串字典序最小的结果,并输出该字符串。 【名词解释】 字典序比较:从字符串第一个字符开始逐个比较,直至出现不同位置,字符较小的一方字典序更小;若一个字符串是另一字符串的前缀,则较短字符串字典序更小。
输入描述
第一行输入一个正整数 n (1≤n≤2×10^5),表示字符串长度。 第二行输入一个由小写字母构成的字符串 s。
输出描述
输出一个字符串,表示经过至多一次操作后可获得的字典序最小字符串。
示例:
输入:
5
baced
输出:
abcde
说明: 在这个样例中,选择三元组 (2,4,1) 可得到结果 “abcde”。
**思路与代码
先尽量让字符串最靠前的位置变小,因为字典序比较先看前面; 设第一次交换把 s[pos1] 和右边距离为 len 的更小字符换过来,之后第二次交换也必须用同样的 len,那就在后半段里找一个影响最小的位置去交换即可。
import sys
def merge(a, b):
if a[0] < b[0]:
return a
if a[0] > b[0]:
return b
if a[1] >= b[1]:
return a
return b
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
s = data[1].decode()
if n < 4:
print(s)
return
a = [ord(c) - 97 for c in s]
size = 1
while size < n:
size <<= 1
inf = (30, -1)
tr = [inf] * (size << 1)
for i in range(n):
tr[size + i] = (a[i], i)
for i in range(size - 1, 0, -1):
tr[i] = merge(tr[i << 1], tr[i << 1 | 1])
def query(left, right):
left += size
right += size
res_left = inf
res_right = inf
while left <= right:
if left & 1:
res_left = merge(res_left, tr[left])
left += 1
if not (right & 1):
res_right = merge(tr[right], res_right)
right -= 1
left >>= 1
right >>= 1
return merge(res_left, res_right)
pos1 = -1
pos2 = -1
for i in range(n):
lim = (n - i - 2) >> 1
if lim <= 0:
continue
left = i + 1
right = i + lim
c, pos = query(left, right)
if c < a[i]:
pos1 = i
pos2 = pos
break
if pos1 == -1:
print(s)
return
length = pos2 - pos1
left = pos2 + 1
right = n - length - 1
pos3 = -1
ok = False
for i in range(left, right + 1):
if a[i + length] < a[i]:
pos3 = i
ok = True
break
if not ok:
for i in range(left, right + 1):
if a[i + length] == a[i]:
pos3 = i
ok = True
break
if not ok:
pos3 = right
res = list(s)
res[pos1], res[pos2] = res[pos2], res[pos1]
res[pos3], res[pos3 + length] = res[pos3 + length], res[pos3]
print(''.join(res))
main()