介绍
豆包青训营是由字节跳动和稀土掘金社区共同发起的技术培训和人才选拔项目,主要面向在校大学生。该项目的目标是培养具有职业竞争力的优秀开发工程师,并提供全程免费的课程,不收取任何费用。
课程内容和方向
豆包青训营的课程涵盖前端、后端和AI方向。在这个飞速发展的AI时代,学员将与豆包MarsCode团队一起深入探索技术领域,学习和运用AI,提高编程效率。此外,课程还包括大数据方向,适合对大数据感兴趣的学员学习,
本文提供训练营试题解析供参考
试题1:小U的数字插入问题
问题描述:
小U手中有两个数字 a 和 b。第一个数字是一个任意的正整数,而第二个数字是一个非负整数。她的任务是将第二个数字 b 插入到第一个数字 a 的某个位置,以形成一个最大的可能数字。
你需要帮助小U找到这个插入位置,输出插入后的最大结果。
def solution(a: int, b: int) -> int:
# 将数字转换为字符串
str_a = str(a)
str_b = str(b)
# 初始化最大值
max_num = 0
# 遍历插入位置
for i in range(len(str_a) + 1):
# 尝试将 str_b 插入到 str_a 的第 i 个位置
new_num_str = str_a[:i] + str_b + str_a[i:]
# 将新字符串转换为整数
new_num = int(new_num_str)
# 更新最大值
if new_num > max_num:
max_num = new_num
return max_num
if __name__ == '__main__':
print(solution(76543, 4) == 765443)
print(solution(1, 0) == 10)
print(solution(44, 5) == 544)
print(solution(666, 6) == 6666)
试题2:我好想逃却逃不掉
问题描述:
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
在一个 N × M 的竞技场迷宫中,你的任务是找出在迷宫中,所有"危险位置"的数量。
"危险位置"定义为:如果站在该位置上,无论采取什么移动策略,都无法到达出口。
竞技场中包含以下几种元素:
.:表示普通地板,可以自由移动到上下左右相邻的格子(不可以走斜线)
O:表示出口
U:表示向上的传送器,踩上去会被强制传送到上方的格子
D:表示向下的传送器,踩上去会被强制传送到下方的格子
L:表示向左的传送器,踩上去会被强制传送到左方的格子
R:表示向右的传送器,踩上去会被强制传送到右方的格子
注意,如果被传送出了竞技场之外,则算作死亡。
输入参数
N: 一个整数,表示竞技场地图的行数
M: 一个整数,表示竞技场地图的列数
data: 一个字符二维数组,表示竞技场地板地图。数组大小为 N × M,其中 1 ≤ N, M ≤ 100
def solution(N, M, data):
# 构建迷宫矩阵
maze = []
for row in data:
if isinstance(row, list):
maze.append(row)
else:
maze.append(list(row))
# 方向映射
directions = {
'U': (-1, 0), # 上
'D': (1, 0), # 下
'L': (0, -1), # 左
'R': (0, 1) # 右
}
def is_valid(x, y):
return 0 <= x < N and 0 <= y < M
def can_reach_exit(start_x, start_y, visited):
# 越界检查
if not is_valid(start_x, start_y):
return False
# 如果已经访问过,说明形成了环
if (start_x, start_y) in visited:
return False
# 到达出口
if maze[start_x][start_y] == 'O':
return True
# 记录当前位置为已访问
visited.add((start_x, start_y))
# 如果是传送点
if maze[start_x][start_y] in directions:
dx, dy = directions[maze[start_x][start_y]]
next_x, next_y = start_x + dx, start_y + dy
return can_reach_exit(next_x, next_y, visited)
# 如果是普通点,尝试四个方向
for dx, dy in directions.values():
next_x, next_y = start_x + dx, start_y + dy
if is_valid(next_x, next_y) and can_reach_exit(next_x, next_y, visited.copy()):
return True
return False
# 统计无法到达出口的位置数量
count = 0
for i in range(N):
for j in range(M):
if maze[i][j] != 'O': # 不考虑出口本身
if not can_reach_exit(i, j, set()):
count += 1
return count
if __name__ == "__main__":
# Add your test cases here
pattern = [
[".", ".", ".", ".", "."],
[".", "R", "R", "D", "."],
[".", "U", ".", "D", "R"],
[".", "U", "L", "L", "."],
[".", ".", ".", ".", "O"]
]
print(solution(5, 5, pattern) == 10)
试题3:小D的‘abc’变换问题
问题描述:
小D拿到了一个仅由 “abc” 三种字母组成的字符串。她每次操作会对所有字符同时进行以下变换:
将 ‘a’ 变成 ‘bc’
将 ‘b’ 变成 ‘ca’
将 ‘c’ 变成 ‘ab’
小D将重复该操作 k 次。你的任务是输出经过 k 次变换后,得到的最终字符串。
例如:对于初始字符串 “abc”,执行 2 次操作后,字符串将变为 “caababbcbcca”。
def solution(s: str, k: int) -> str:
def transform(s: str) -> str:
# 初始化结果字符串
result = []
# 遍历输入字符串的每个字符
for char in s:
# 根据字符进行变换
if char == 'a':
result.append('bc')
elif char == 'b':
result.append('ca')
elif char == 'c':
result.append('ab')
# 将结果列表拼接成字符串
return ''.join(result)
# 初始化当前字符串为输入字符串
current_string = s
# 进行 k 次变换
for _ in range(k):
current_string = transform(current_string)
return current_string
if __name__ == '__main__':
print(solution("abc", 2) == 'caababbcbcca')
print(solution("abca", 3) == 'abbcbccabccacaabcaababbcabbcbcca')
print(solution("cba", 1) == 'abcabc')
试题4:组成字符串ku的最大次数
问题描述:
给定一个字符串
s
s,该字符串中只包含英文大小写字母。你需要计算从字符串中最多能组成多少个字符串 “ku”。每次可以随机从字符串中选一个字符,并且选中的字符不能再使用。字符串中的字符大小写可以忽略,即大写和小写字母视为相同。
例如,输入 “AUBTMKAxfuu”,从中最多能组成 1 个 “ku”。
public class Main {
public static int solution(String s) {
// 将字符串转换为小写,以便忽略大小写
s = s.toLowerCase();
// 初始化计数器
int kCount = 0;
int uCount = 0;
// 遍历字符串,统计 'k' 和 'u' 的出现次数
for (char c : s.toCharArray()) {
if (c == 'k') {
kCount++;
} else if (c == 'u') {
uCount++;
}
}
// 计算最多能组成的 "ku" 的数量
// 关键步骤:返回 kCount 和 uCount 中的较小值
return Math.min(kCount, uCount);
}
public static void main(String[] args) {
System.out.println(solution("AUBTMKAxfuu") == 1);
System.out.println(solution("KKuuUuUuKKKKkkkkKK") == 6);
System.out.println(solution("abcdefgh") == 0);
}
}
试题5:二分数字组合
问题描述:
小F面临一个有趣的挑战:给定一个数组,她需要将数组中的数字分为两组。分组的目标是使得一组数字的和的个位数等于给定的 A,另一组数字的和的个位数等于给定的 B。除此之外,还有一种特殊情况允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。小F需要计算所有可能的划分方式。
例如,对于数组 [1, 1, 1] 和目标 A = 1,B = 2,可行的划分包括三种:每个 1 单独作为一组,其余两个 1 形成另一组。如果 A = 3,B = 5,当所有数字加和的个位数为 3 或 5 时,可以有一组为非空,另一组为空。
import java.util.ArrayList;
import java.util.List;
public class Main {
// 计算所有子序列的和
public static List<Integer> findAllSubsequenceSums(int[] array_a) {
List<Integer> sums = new ArrayList<>();
backtrack(array_a, 0, 0, 0, sums); // 添加一个参数表示当前子序列的大小
return sums;
}
// 回溯法生成所有子序列和
private static void backtrack(int[] array_a, int index, int currentSum, int size, List<Integer> sums) {
// 只在子序列非空时添加和
if (size > 0) {
sums.add(currentSum);
}
// 遍历每个元素,生成子序列
for (int i = index; i < array_a.length; i++) {
currentSum += array_a[i]; // 选择当前元素
backtrack(array_a, i + 1, currentSum, size + 1, sums); // 递归调用
currentSum -= array_a[i]; // 撤销选择
}
}
// 主要解决方案
public static int solution(int n, int A, int B, int[] array_a) {
int totalSum = 0;
for (int num : array_a) {
totalSum += num; // 计算总和
}
List<Integer> sums = findAllSubsequenceSums(array_a);
int count = 0;
// 遍历所有子序列和
for (int sum : sums) {
// 排除空子序列和整个原始序列的情况
if (sum % 10 == A && sum != totalSum) {
// 检查总和减去当前子序列和的个位数
if ((totalSum - sum) % 10 == B) {
count++; // 计数符合条件的分组
}
}
}
// 检查总和的个位数是否等于 A 或 B
if (totalSum % 10 == A || totalSum % 10 == B) {
count++; // 计数特殊情况
}
return count; // 返回结果
}
public static void main(String[] args) {
// 测试用例
int[] array1 = {1, 1, 1};
int[] array2 = {1, 1, 1};
int[] array3 = {1, 1};
// 输出验证
System.out.println(solution(3, 1, 2, array1) == 3);
System.out.println(solution(3, 3, 5, array2) == 1);
System.out.println(solution(2, 1, 1, array3) == 2);
}
}
试题6:游戏排名第三大的分数
问题描述:
小M想要通过查看往届游戏比赛的排名来确定自己比赛的目标分数。他希望找到往届比赛中排名第三的分数,作为自己的目标。具体规则如下:
如果分数中有三个或以上不同的分数,返回其中第三大的分数。
如果不同的分数只有两个或更少,那么小M将选择最大的分数作为他的目标。
请你帮小M根据给定的分数数组计算目标分数。
def solution(n: int, nums: list) -> int:
# 去重
unique_nums = list(set(nums))
# 降序排序
unique_nums.sort(reverse=True)
# 判断第三大的分数
if len(unique_nums) >= 3:
return unique_nums[2] # 返回第三大的分数
else:
return unique_nums[0] # 返回最大的分数
if __name__ == '__main__':
print(solution(3, [3, 2, 1]) == 1)
print(solution(2, [1, 2]) == 2)
print(solution(4, [2, 2, 3, 1]) == 1)
试题7:小S的倒排索引
问题描述:
小S正在帮助她的朋友们建立一个搜索引擎。为了让用户能够更快地找到他们感兴趣的帖子,小S决定使用倒排索引。倒排索引的工作原理是:每个单词都会关联一个帖子ID的列表,这些帖子包含该单词,且ID按从小到大的顺序排列。
例如,单词“夏天”可能出现在帖子1、帖子3和帖子7中,那么这个单词的倒排链就是 [1, 3, 7]。如果用户想同时找到包含“夏天”和“海滩”的帖子,小S需要找出两个倒排链的交集,且将结果按照从大到小的顺序输出。现在,给定两个单词的倒排链数组 a 和 b,请你帮助小S找出同时包含这两个单词的帖子ID,并按从大到小的顺序返回结果。
def solution(a, b):
# 初始化两个指针
i, j = 0, 0
# 初始化结果列表
result = []
# 使用双指针法找出交集
while i < len(a) and j < len(b):
if a[i] == b[j]:
# 如果元素相等,加入结果列表
result.append(a[i])
i += 1
j += 1
elif a[i] < b[j]:
# 如果a[i]小于b[j],移动a的指针
i += 1
else:
# 如果a[i]大于b[j],移动b的指针
j += 1
# 反转结果列表以满足从大到小的顺序要求
result.reverse()
return result
if __name__ == '__main__':
print(solution([1, 2, 3, 7], [2, 5, 7]) == [7, 2])
print(solution([1, 4, 8, 10], [2, 4, 8, 10]) == [10, 8, 4])
print(solution([3, 5, 9], [1, 4, 6]) == [])
print(solution([1, 2, 3], [1, 2, 3]) == [3, 2, 1])
试题8:补给站最优花费问题
问题描述:
小U计划进行一场从地点A到地点B的徒步旅行,旅行总共需要 M 天。为了在旅途中确保安全,小U每天都需要消耗一份食物。在路程中,小U会经过一些补给站,这些补给站分布在不同的天数上,且每个补给站的食物价格各不相同。
小U需要在这些补给站中购买食物,以确保每天都有足够的食物。现在她想知道,如何规划在不同补给站的购买策略,以使她能够花费最少的钱顺利完成这次旅行。
M:总路程所需的天数。
N:路上补给站的数量。
p:每个补给站的描述,包含两个数字 A 和 B,表示第 A 天有一个补给站,并且该站每份食物的价格为 B 元。
保证第0天一定有一个补给站,并且补给站是按顺序出现的。
def solution(m: int, n: int, p: list[list[int]]) -> int:
# 动态规划问题(dp)
# 思路:遍历每一天,若到达有补给站的那天,判断当天与之前的每天价格的大小,选择最小的值作为当日食物费用。
# 定义最小成本 和 当前最小成本
min_cost = 0
min_now = p[0][1] # 让当前的最小值等于第0天的价格
# 遍历每一天
for day in range(m):
# 遍历每一个补给站描述
# 当有补给站时,计算最小价格
if day in [station[0] for station in p]: # day是二元数组p中的第一个数组中的第一个数字,代表第几天
# 找到 p数组中,第 day天时,其对应的价格 并记录下来
for station in p:
if station[0] == day:
result = station[1]
# 找最小值:判断第day天的价格 和 前几天的最小价格,哪个更小
min_now = min(min_now, result)
min_cost += min_now
# 当没有补给站时,令该天食物价格为之前的最小值
else:
min_cost += min_now
return min_cost
if __name__ == "__main__":
print(solution(5, 4, [[0, 2], [1, 3], [2, 1], [3, 2]]) == 7)
print(solution(6 ,5 , [[0, 1], [1, 5], [2, 2], [3, 4], [5, 1]]))
print(solution(4 , 3 ,[[0, 3], [2, 2], [3, 1]]) )
试题9:计算从位置x到y的最少步数
问题描述:
小F正在进行一个 AB 实验,需要从整数位置 x 移动到整数位置 y。每一步可以将当前位置增加或减少,且每步的增加或减少的值必须是连续的整数(即每步的移动范围是上一步的 -1,+0 或 +1)。首末两步的步长必须是 1。求从 x 到 y 的最少步数。
输入描述
输入包含两个整数 x 和 y,表示起始位置和目标位置。
输出描述
输出从 x 到 y 所需的最小步数。
def solution(x_position, y_position):
res = abs(x_position - y_position)
cnt = 1
while res > cnt * 2:
res -= cnt * 2
cnt += 1
if res == 0:
return cnt * 2 - 2
elif res <= cnt:
return cnt * 2 - 1
elif cnt < res <= cnt * 2:
return cnt * 2
if __name__ == "__main__":
# You can add more test cases here
print(solution(12, 6) == 4 )
print(solution(34, 45) == 6)
print(solution(50, 30) == 8)
试题10:换装DNA序列的最小表示法
问题描述:
小C正在研究一种环状的 DNA 结构,它由四种碱基A、C、G、T构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 n 的碱基序列可以有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。
例如:碱基序列 ATCA 从不同位置读取可能的表示有 ATCA, TCAA, CAAT, AATC,其中 AATC 是字典序最小的表示。
def solution(dna_sequence):
# 将原始序列复制并拼接成两倍长度的序列
extended_sequence = dna_sequence + dna_sequence
# 初始化最小序列
min_sequence = extended_sequence[:len(dna_sequence)]
# 遍历所有可能的起始位置
for i in range(1, len(dna_sequence)):
# 获取从当前位置开始的子序列
current_sequence = extended_sequence[i:i+len(dna_sequence)]
# 比较当前子序列和最小序列
if current_sequence < min_sequence:
# 更新最小序列
min_sequence = current_sequence
return min_sequence
if __name__ == "__main__":
# 你可以添加更多测试用例
print(solution("ATCA") == "AATC")
print(solution("CGAGTC") == "AGTCCG")
print(solution("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG") == "AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG")