数字分组求偶数和
问题描述
小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。
numbers
: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。
例如对于[123, 456, 789]
,14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369
。
测试样例
样例1:
输入:
numbers = [123, 456, 789]
输出:14
样例2:
输入:
numbers = [123456789]
输出:4
样例3:
输入:
numbers = [14329, 7568]
输出:10
JavaScript代码
(含注释&要点)
// 目标: 分组内的任选一个数,各组各数之和 得偶数。有几种分法。
// 思路:
// ① 将每个数字组拆成单个数字
// ② 记录每个数字的奇偶性
// ③ 递归/回溯 从每个数字组中选择一个数字,并检查各数之和是否为偶数
// ④ 计算结果:递归的终止条件中,检查当前组合的各数之和是否为偶数。
function solution(numbers) {
// ① 将每个数字拆成单个数字(法一):第二个map, ★(.map(Number))将其转为数字类型
// let digits = numbers.map(numStr => (numStr + '').split('').map(Number))
// console.log(digits)
// ① 将每个数字拆成单个数字(法二):
let n = numbers.length;
let str = [];
let result = 0;
for (let i = 0; i < n; i++) {
let t = [];
t = (numbers[i] + '').split('');
str.push(t.map(Number));
}
console.log(str)
// ② 记录每组数字的奇偶性.数组1为奇数,0为偶数
let parity = str.map(group => group.map(x => x % 2))
// ③ 组合计算
// 问题:如何表示第一个数组中的某个数与其他的数组相加
// 解决:★递归/回溯!!
// ③ 使用回溯法或组合生成法,从每个数字组中选择一个数字,并检查其各位之和是否为偶数
function backtrack(index, currentSum) {
// 终止条件:若已选择了所有数字组,检查当前组合的各位之和是否为偶数(做判断 是在终止条件内)
if (index === str.length) {
return currentSum % 2 === 0 ? 1 : 0;
}
let count = 0;
// 选择当前的数字组内的元素
for (let num of str[index]) {
// 递归调用,选择下一个数字组中的数字
count += backtrack(index + 1, currentSum + num);
}
return count;
}
// ④ 统计符合条件的组合资源
return backtrack(0, 0)
}
function main() {
console.log(solution([123, 456, 789]));
console.log(solution([123, 456, 789]) === 14);
console.log(solution([123456789]) === 4);
console.log(solution([14329, 7568]) === 10);
}
main();
Java代码
import java.util.ArrayList;
import java.util.List;
public class Main {
public static int solution(int[] numbers) {
// 思路:
// ① 将每组数字拆成单个数字
// ② 记录每个数字的奇偶性
// ③ 递归/回溯,从每个数字组中选择一个数字,并检查各数之和是否为偶数
// ④ 计算结果:递归的终止条件中,检查当前组合的各数之和是否为偶数
int n = numbers.length;
List<List<Integer>> digits = new ArrayList<>();
for(int i = 0;i < n;i++){
List<Integer> group = new ArrayList<>();
char[] charArr = Integer.toString(numbers[i]).toCharArray(); //将数字转为字符数组
for(int j = 0;j < charArr.length;j++){
group.add(charArr[j]-'0');
}
digits.add(group);
}
return backtrack(digits, 0, 0);
}
// 组合选择,用递归、回溯的方法,从数字组中选择一个数字,并检查各位之和是否为偶数
private static int backtrack(List<List<Integer>> digits, int index, int currentSum){
// 终止条件:若已选择了所有数字组
if(index == digits.size()) {
return currentSum % 2 == 0 ? 1 : 0;
}
int count = 0;
for(int num : digits.get(index)){ // 增强for循环;List特有方法get、set、add、remove
// 递归调用,选择下一个数字组中的数字
count += backtrack(digits, index+1, currentSum + num);
}
return count;
}
public static void main(String[] args) {
System.out.println(solution(new int[]{123, 456, 789}) == 14);
System.out.println(solution(new int[]{123456789}) == 4);
System.out.println(solution(new int[]{14329, 7568}) == 10);
}
}
创意标题匹配问题
问题描述
在广告平台中,为了给广告主一定的自由性和效率,允许广告主在创造标题的时候以通配符的方式进行创意提交。线上服务的时候,会根据用户的搜索词触发的 bidword 对创意中的通配符(通配符是用成对 {} 括起来的字符串,可以包含 0 个或者多个字符)进行替换,用来提升广告投放体验。例如:“{末日血战} 上线送 SSR 英雄,三天集齐无敌阵容!”,会被替换成“帝国时代游戏下载上线送 SSR 英雄,三天集齐无敌阵容!”。给定一个含有通配符的创意和n个标题,判断这句标题是否从该创意替换生成的。
测试样例
样例1:
输入:
n = 4, template = "ad{xyz}cdc{y}f{x}e", titles = ["adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe"]
输出:"True,False,False,True"
样例2:
输入:
n = 3, template = "a{bdc}efg", titles = ["abcdefg", "abefg", "efg"]
输出:"True,True,False"
样例3:
输入:
n = 5, template = "{abc}xyz{def}", titles = ["xyzdef", "abcdef", "abxyzdef", "xyz", "abxyz"]
输出:"True,False,True,True,True"
Java代码
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class Main {
public static String solution(int n, String template, String[] titles) {
// n个测试样例,template 通配符,titles模板
// 思路:① 记录通配符,② 匹配
// 将模板转换为正则表达式,并确保匹配整个字符串
String regex = template.replaceAll("\\{[^}]*}",".*");
regex = "^" + regex + "$";
// 创建一个 StringBuilder 来存储结果
StringBuilder result = new StringBuilder();
// 遍历每个标题。若匹配成功,添加true;失败则添加false
for(String title: titles) {
result.append( Pattern.matches(regex,title) ? "True" : "False").append(",");
}
// 删除最后一个逗号
if(result.length() > 0) {
result.deleteCharAt(result.length()-1);
}
return result.toString();
}
public static void main(String[] args) {
// You can add more test cases here
String[] testTitles1 = {"adcdcefdfeffe", "adcdcefdfeff", "dcdcefdfeffe", "adcdcfe"};
String[] testTitles2 = {"CLSomGhcQNvFuzENTAMLCqxBdj", "CLSomNvFuXTASzENTAMLCqxBdj", "CLSomFuXTASzExBdj", "CLSoQNvFuMLCqxBdj", "SovFuXTASzENTAMLCq", "mGhcQNvFuXTASzENTAMLCqx"};
String[] testTitles3 = {"abcdefg", "abefg", "efg"};
System.out.println(solution(4, "ad{xyz}cdc{y}f{x}e", testTitles1).equals("True,False,False,True"));
System.out.println(solution(6, "{xxx}h{cQ}N{vF}u{XTA}S{NTA}MLCq{yyy}", testTitles2).equals("False,False,False,False,False,True"));
System.out.println(solution(3, "a{bdc}efg", testTitles3).equals("True,True,False"));
}
}
补给站最优花费问题
问题描述
小U计划进行一场从地点A到地点B的徒步旅行,旅行总共需要 M
天。为了在旅途中确保安全,小U每天都需要消耗一份食物。在路程中,小U会经过一些补给站,这些补给站分布在不同的天数上,且每个补给站的食物价格各不相同。
小U需要在这些补给站中购买食物,以确保每天都有足够的食物。现在她想知道,如何规划在不同补给站的购买策略,以使她能够花费最少的钱顺利完成这次旅行。
M
:总路程所需的天数。N
:路上补给站的数量。p
:每个补给站的描述,包含两个数字A
和B
,表示第A
天有一个补给站,并且该站每份食物的价格为B
元。
保证第0天一定有一个补给站,并且补给站是按顺序出现的。
测试样例
样例1:
输入:
m = 5 ,n = 4 ,p = [[0, 2], [1, 3], [2, 1], [3, 2]]
输出:7
样例2:
输入:
m = 6 ,n = 5 ,p = [[0, 1], [1, 5], [2, 2], [3, 4], [5, 1]]
输出:6
样例3:
输入:
m = 4 ,n = 3 ,p = [[0, 3], [2, 2], [3, 1]]
输出:9
Python代码
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):
# 遍历每一个补给站描述。day是二元数组p中的第一个数组中的第一个数字,代表第几天
if day in [station[0] for station in 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]]) )
★蛇形填充n阶方阵(易)
问题描述
小U面临一个有趣的任务:在一个 n×n 的方阵中填入 11到 n×n 这些数字,并要求按照蛇形顺序从右上角开始,沿着方阵的边界顺时针进行填充。蛇形填充的特殊排列方式使得每一层数字呈现出波浪形的排列方式。
例如,当 n=4 时,方阵应如下所示:
10 11 12 1
9 16 13 2
8 15 14 3
7 6 5 4
你需要编写程序输出填充后的方阵,确保格式的整齐性。
测试样例
样例1:
输入:
n = 4
输出:[[10, 11, 12, 1], [9, 16, 13, 2], [8, 15, 14, 3], [7, 6, 5, 4]]
样例2:
输入:
n = 5
输出:[[13, 14, 15, 16, 1], [12, 23, 24, 17, 2], [11, 22, 25, 18, 3], [10, 21, 20, 19, 4], [9, 8, 7, 6, 5]]
样例3:
输入:
n = 3
输出:[[7, 8, 1], [6, 9, 2], [5, 4, 3]]
解题思路
- 初始化矩阵:创建一个
n x n
的矩阵,并初始化为0
。 - 定义填充方向:我们需要定义四个方向:右、下、左、上。
- 填充数字:从右上角开始,按照蛇形顺序填充数字。
- 边界处理:在填充过程中,需要注意矩阵的边界和已经填充过的位置。
具体步骤
-
初始化矩阵:使用列表推导式创建一个
n x n
的矩阵,所有元素初始化为0
-
定义方向:使用一个列表
directions
来存储四个方向的偏移量。 -
初始位置和方向:从右上角开始,初始方向为右。
-
填充数字:
- 使用一个循环从
1
到n * n
填充数字。 - 在每次填充后,计算下一个位置。
- 检查下一个位置是否越界或已经填充过,如果是,则改变方向。
- 使用一个循环从
Python代码
def solution(n: int) -> list:
# 初始化一个 n x n 的矩阵,所有元素为 0
matrix = [[0] * n for _ in range(n)]
# 定义四个方向:右、下、左、上
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
# 初始位置和方向
x, y = 0, n - 1 # 从右上角开始
direction_index = 0 # 初始方向为右
# 填充数字
for num in range(1, n * n + 1):
matrix[x][y] = num
# 计算下一个位置
next_x = x + directions[direction_index][0]
next_y = y + directions[direction_index][1]
# 检查下一个位置是否越界或已经填充过
if not (0 <= next_x < n and 0 <= next_y < n and matrix[next_x][next_y] == 0):
# 改变方向
direction_index = (direction_index + 1) % 4
next_x = x + directions[direction_index][0]
next_y = y + directions[direction_index][1]
# 更新当前位置
x, y = next_x, next_y
return matrix
if __name__ == '__main__':
print(solution(4) == [[10, 11, 12, 1], [9, 16, 13, 2], [8, 15, 14, 3], [7, 6, 5, 4]])
print(solution(5) == [[13, 14, 15, 16, 1], [12, 23, 24, 17, 2], [11, 22, 25, 18, 3], [10, 21, 20, 19, 4], [9, 8, 7, 6, 5]])
print(solution(3) == [[7, 8, 1], [6, 9, 2], [5, 4, 3]])
说明
1. 方向问题:从右上开始,按顺时针的顺序,我们以左、下为 x、y轴的正方向。
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] 为顺时针顺序。
2.
directions[direction_index][0]
和directions[direction_index][1]
:
directions
是一个包含四个方向的列表,每个方向由一个元组表示,元组的第一个元素表示行偏移量,第二个元素表示列偏移量。direction_index
是一个索引,表示当前的方向。directions[direction_index][0]
表示当前方向的行偏移量。directions[direction_index][1]
表示当前方向的列偏移示例
假设当前方向是向右(
direction_index = 0
),那么:
directions[0]
是(0, 1)
,表示行偏移量为0
,列偏移量为1
。- 如果当前位置是
(x, y)
,那么next_x = x + 0
,next_y = y + 1
,即下一个位置是(x, y + 1)
。
小M的能力选择挑战(易)- DFS
问题描述
小M最近在玩一款叫做“狼人拯救者”的游戏。在游戏中,玩家可以通过消耗金币来购买能力,这些能力会增加攻击力,但也会影响攻击速度。
小M是一个以攻击力为优先的玩家,但他必须保证自己的攻击速度不能低于0,因为攻击速度为负时将无法攻击。
现在小M面对商店中的N种能力,他拥有G枚金币和S点初始攻击速度。他想知道,在保持攻击速度大于等于0的前提下,他最多可以获得多少攻击力。
商店中每种能力用三元组表示为 array[i] = [c, s, d]
,其中:
c
表示购买该能力需要的金币数;s
表示该能力对攻击速度的影响,可以为正数、零或负数;d
表示该能力对攻击力的增加值。
测试样例
样例1:
输入:
n = 6 ,g = 100 ,s = 100 ,array = [[71, -51, 150], [40, 50, 100], [40, 50, 100], [30, 30, 70], [30, 30, 70], [30, 30, 70]]
输出:240
样例2:
输入:
n = 6 ,g = 100 ,s = 100 ,array = [[71, -51, 150], [40, -50, 100], [40, -50, 100], [30, -30, 70], [30, -30, 70], [30, -30, 70]]
输出:210
样例3:
输入:
n = 5 ,g = 50 ,s = 50 ,array = [[25, -25, 100], [25, -25, 50], [20, 0, 60], [15, -15, 40], [10, -5, 30]]
输出:170
思路
采用 深度优先搜索(DFS)来解决这个问题。DFS 是一种递归算法,通过遍历所有可能的选择来找到最优解。
-
初始化:
- 初始化一个变量
max_attack
来存储当前找到的最大攻击力。 - 调用
dfs
函数来开始递归搜索。
- 初始化一个变量
-
递归搜索:
- 在
dfs
函数中,递归地遍历每一种能力,决定是否选择该能力。 - 如果选择该能力,更新当前的金币数、攻击速度和攻击力,然后继续递归搜索。
- 如果不选择该能力,直接跳到下一个能力。
- 在
-
递归出口:
- 当遍历完所有能力后,检查当前的攻击力是否大于
max_attack
,如果是,则更新max_attack
。
- 当遍历完所有能力后,检查当前的攻击力是否大于
互斥性:在同一层递归中,只会执行其中一个递归调用。
- 如果条件
if g >= cost and s + speed_change >= 0:
满足,则执行“选择当前能力的dfs
”。- 如果条件不满足,则执行“不选择当前能力的
dfs
”。
Python代码
def solution(n, g, s, array):
# n:能力
# g:拥有的金币
# s:初始攻击速度
# array,三元组,表示 [c 购买的金币数,s攻击速度,d攻击力的增加值]
# 目标:保持 s 攻击速度 >=0 前提下,求v最大攻击力max_d
# 思路:遍历每一种能力,对比,添加上某一款能力 比 不添加哪一款能力,哪个攻击力大
# 最大攻击性
max_attack = [0]
dfs(n, g, s, 0, 0, array, max_attack)
return max_attack[0]
def dfs(n, g, s, current_attack, current_index, abilities, max_attack):
# 出口:如果是最后一种能力,
# 当前的攻击性大于最大攻击性,则更新最大攻击性
if current_index == n:
if current_attack > max_attack[0]:
max_attack[0] = current_attack
return
# 如不选择当前能力,则直接递归调用,跳到下一个能力
dfs(n, g, s, current_attack, current_index + 1, abilities, max_attack)
# 解构,金币成本、攻击速度、攻击力
cost, speed_change, attack_change = abilities[current_index]
# 如果选择当前能力,检查是否满足条件
# 当前的金币能购买,并且攻击速度大于等于0,若符合条件,更新金币数、攻击速度、攻击力,然后递归下一个
if g >= cost and s + speed_change >= 0:
dfs(n, g - cost, s + speed_change, current_attack + attack_change, current_index + 1, abilities, max_attack)
if __name__ == "__main__":
# test1 = [
# [71, 51, 150],
# [40, 50, 100],
# [40, 50, 100],
# [30, 30, 70],
# [30, 30, 70],
# [30, 30, 70],
# ]
# print(solution(6, 100, 100, test1) ) #== 240)
# print(solution( 6 ,100 , 100 ,[[71, -51, 150], [40, 50, 100], [40, 50, 100], [30, 30, 70], [30, 30, 70], [30, 30, 70]]) ) #== 240)
test1 = [
[71, -51, 150],
[40, -50, 100],
[40, -50, 100],
[30, -30, 70],
[30, -30, 70],
[30, -30, 70],
]
print(solution(6, 100, 100, test1)) # == 210)
数字字符串格式化(易)
问题描述
小M在工作时遇到了一个问题,他需要将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式,并且保留小数部分。小M还发现,有时候输入的数字字符串前面会有无用的 0
,这些也需要精简掉。请你帮助小M编写程序,完成这个任务。
测试样例
样例1:
输入:
s = "1294512.12412"
输出:'1,294,512.12412'
样例2:
输入:
s = "0000123456789.99"
输出:'123,456,789.99'
样例3:
输入:
s = "987654321"
输出:'987,654,321'
Java代码
public class Main {
public static String solution(String s) {
// 去前头的零
s = s.replaceFirst("^0+(?!$)","");
// 分离整数和小数
String [] parts = s.split("\\.");
String intPart = parts[0];
String decimalPart = parts.length > 1 ? "." + parts[1] : "";
// 用 StringBuilder 来构建带千分位逗号地整数部分
StringBuilder formattedIntPart = new StringBuilder();
int len = intPart.length();
for (int i = 0; i < len; i++) {
if (i > 0 && (len - i) % 3 == 0) {
// 在这里添加逗号
formattedIntPart.append(",");
}
// 添加当前字符
formattedIntPart.append(intPart.charAt(i));
}
// 合并整数和小数
return formattedIntPart.toString() + decimalPart;
}
public static void main(String[] args) {
System.out.println(solution("1294512.12412").equals("1,294,512.12412"));
System.out.println(solution("0000123456789.99").equals("123,456,789.99"));
System.out.println(solution("987654321").equals("987,654,321"));
}
}
小M的多任务下载器挑战(易)
106
Java代码:
import java.util.TreeMap;
public class Main {
public static int solution(int n, int[][] array) {
// 使用 TreeMap 来记录每个时刻正在进行的任务数量
TreeMap<Integer, Integer> taskCountAtEachSecond = new TreeMap<>();
// 遍历输入的任务数组
for (int[] task : array) {
int start = task[0];
int duration = task[1];
// 从任务开始到结束的每个时刻,任务数量加 1
for (int i = start; i < start + duration; i++) {
if (taskCountAtEachSecond.containsKey(i)) {
taskCountAtEachSecond.put(i, taskCountAtEachSecond.get(i) + 1);
} else {
taskCountAtEachSecond.put(i, 1);
}
}
}
// 找到任务数量的最大值
int maxConcurrentTasks = 0;
for (Integer count : taskCountAtEachSecond.values()) {
if (count > maxConcurrentTasks) {
maxConcurrentTasks = count;
}
}
return maxConcurrentTasks;
}
public static void main(String[] args) {
// Add your test cases here
System.out.println(solution(2, new int[][] { { 1, 2 }, { 2, 3 } }) == 2);
System.out.println(solution(4, new int[][] { { 1, 2 }, { 2, 3 }, { 3, 5 }, { 4, 3 } }) == 3);
}
}
小M的37进制数字相乘问题(中)
194
在处理更大的37进制数时,比如
ZZ
,十进制值会变得非常大。这可能导致溢出问题,因为long
类型在 Java 中最大值为 263−12^{63} - 1263−1,约等于 9×10189 \times 10^{18}9×1018。当ZZ
转换为十进制时,其值为 35×37+35=133235 \times 37 + 35 = 133235×37+35=1332,但在更复杂的场景中数值会快速增长。为了解决这个问题,可以使用 Java 的
BigInteger
类型,它支持任意大小的整数运算。
关键点:
使用
BigInteger
:
- 将所有整数计算改为
BigInteger
类型,以支持超大数值的计算。处理
BigInteger
的乘法和进制转换:
- 37进制转十进制:通过
decimal = decimal.multiply(base).add(value)
累积计算。- 十进制转37进制:通过
num.divideAndRemainder(base)
获得商和余数。
import java.math.BigInteger;
public class Base37Multiplier {
// 字符集
private static final String CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ$";
// 将37进制字符串转换为十进制整数(使用 BigInteger)
private static BigInteger base37ToDecimal(String num) {
BigInteger decimal = BigInteger.ZERO;
BigInteger base = BigInteger.valueOf(37);
for (int i = 0; i < num.length(); i++) {
char c = num.charAt(i);
int value = CHARSET.indexOf(c); // 获取字符对应的数值
decimal = decimal.multiply(base).add(BigInteger.valueOf(value));
}
return decimal;
}
// 将十进制整数转换为37进制字符串(使用 BigInteger)
private static String decimalToBase37(BigInteger num) {
if (num.equals(BigInteger.ZERO)) {
return "0";
}
StringBuilder base37 = new StringBuilder();
BigInteger base = BigInteger.valueOf(37);
while (num.compareTo(BigInteger.ZERO) > 0) {
BigInteger[] divAndRem = num.divideAndRemainder(base);
base37.append(CHARSET.charAt(divAndRem[1].intValue())); // 根据余数获取字符
num = divAndRem[0]; // 更新为商
}
return base37.reverse().toString(); // 反转结果
}
public static String multiplyBase37(String a, String b) {
// 转换为十进制
BigInteger num1 = base37ToDecimal(a);
BigInteger num2 = base37ToDecimal(b);
// 计算乘积
BigInteger product = num1.multiply(num2);
// 转换回37进制
return decimalToBase37(product);
}
public static void main(String[] args) {
// 测试样例
System.out.println(multiplyBase37("Z", "X")); // 输出:V8
System.out.println(multiplyBase37("A", "B")); // 输出:2$
System.out.println(multiplyBase37("ZZ", "X")); // 处理大数,输出:3LCG
}
}
参考:青训营链接【✍️ 入营考核】AI 加码,青训营 X 豆包MarsCode 技术训练营👏 欢迎各位同学报名“AI 加码,青训营 X - 掘金 (juejin.cn)
数字分组求偶数和_2024字节跳动青训营中数字分组求偶数和的答案-CSDN博客