第七届传智杯程序设计模拟题(java解答)
10.小F的永久代币卡回本计划
(签到 简单模拟)
问题描述
小F最近迷上了玩一款游戏,她面前有一个永久代币卡的购买机会。该卡片的价格为 a
勾玉,每天登录游戏可以返还 b
勾玉。小F想知道她至少需要登录多少天,才能让购买的永久代币卡回本。
测试样例
样例1:
输入:a = 10, b = 1
输出:10
样例2:
输入:a = 10, b = 2
输出:5
样例3:
输入:a = 10, b = 3
输出:4
// 导入必要的包
import java.util.Scanner;
public class Solution {
// 定义solution方法
public static int solution(int a, int b) {
// 计算需要登录的天数
int days = a / b;
if (a % b != 0) {
days++;
}
return days;
}
// 主方法
public static void main(String[] args) {
// 创建Scanner对象以读取用户输入
Scanner scanner = new Scanner(System.in);
// 提示用户输入代币卡的价格
System.out.print("请输入代币卡的价格 (a): ");
int a = scanner.nextInt();
// 提示用户输入每天返还的勾玉数量
System.out.print("请输入每天返还的勾玉数量 (b): ");
int b = scanner.nextInt();
// 调用solution方法并输出结果
int result = solution(a, b);
System.out.println("需要登录的天数: " + result);
// 关闭Scanner对象
scanner.close();
}
}
中16最大矩研形面积问题
(队伍遍历整个数组)
问题描述
小S最近在分析一个数组 h1,h2,...,hNh1,h2,...,hN,数组的每个元素代表某种高度。小S对这些高度感兴趣的是,当我们选取任意 kk 个相邻元素时,如何计算它们所能形成的最大矩形面积。
对于 kk 个相邻的元素,我们定义其矩形的最大面积为:
R(k)=k×min(h[i],h[i+1],...,h[i+k−1])R(k)=k×min(h[i],h[i+1],...,h[i+k−1])
即,R(k)R(k) 的值为这 kk 个相邻元素中的最小值乘以 kk。现在,小S希望你能帮他找出对于任意 kk,R(k)R(k) 的最大值。
测试样例
样例1:
输入:n = 5, array = [1, 2, 3, 4, 5]
输出:9
样例2:
输入:n = 6, array = [5, 4, 3, 2, 1, 6]
输出:9
样例3:
输入:n = 4, array = [4, 4, 4, 4]
输出:16
public class Main {
public static int solution(int n, int[] array) {
int maxArea = 0; // 用于记录当前的最大面积
// 遍历所有可能的 k 值,从 1 到 n
for (int k = 1; k <= n; k++) {
// 遍历数组,计算以每个元素为起点的 k 个相邻元素的最小值乘以 k
for (int i = 0; i <= n - k; i++) {
int minHeight = array[i]; // 初始化最小高度为当前元素的高度
// 遍历当前 k 个相邻元素,找到最小高度
for (int j = 1; j < k; j++) {
if (array[i + j] < minHeight) {
minHeight = array[i + j];
}
}
int area = k * minHeight; // 计算当前 k 个相邻元素的面积
if (area > maxArea) { // 如果当前面积大于最大面积,则更新最大面积
maxArea = area;
}
}
}
return maxArea; // 返回最大面积
}
public static void main(String[] args) {
// 添加测试用例
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}) == 9);
System.out.println(solution(6, new int[]{5, 4, 3, 2, 1, 6}) == 9);
System.out.println(solution(4, new int[]{4, 4, 4, 4}) == 16);
}
}
(动态规划)易 25DNA序列编辑距离
(动态规划)
问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
测试样例
样例1:
输入:dna1 = "AGT",dna2 = "AGCT"
输出:1
样例2:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
样例3:
输入:dna1 = "ACGT",dna2 = "TGC"
输出:3
样例4:
输入:dna1 = "A",dna2 = "T"
输出:1
样例5:
输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4
public class Main {
public static int solution(String dna1, String dna2) {
// 获取两个序列的长度
int m = dna1.length();
int n = dna2.length();
// 创建一个二维数组来存储子问题的解
// dp[i][j] 表示将dna1的前i个字符转换为dna2的前j个字符所需的最小编辑步数
int[][] dp = new int[m + 1][n + 1];
// 初始化边界条件
for (int i = 0; i <= m; i++) {
dp[i][0] = i; // 删除所有字符以匹配空字符串
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j; // 插入所有字符以匹配空字符串
}
// 填充dp数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果当前字符相同,则不需要额外操作
if (dna1.charAt(i - 1) == dna2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
// 取三种操作中的最小值加一
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
}
}
}
// 返回结果
return dp[m][n];
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution("AGCTTAGC", "AGCTAGCT") == 2); // 输出: true
System.out.println(solution("AGCCGAGC", "GCTAGCT") == 4); // 输出: true
System.out.println(solution("AGT", "AGCT")); // 输出: 1
System.out.println(solution("AACCGGTT", "AACCTTGG")); // 输出: 4
System.out.println(solution("ACGT", "TGC")); // 输出: 3
System.out.println(solution("A", "T")); // 输出: 1
System.out.println(solution("GGGG", "TTTT")); // 输出: 4
}
}
解析
- 初始化:
-
m
和n
分别是两个DNA序列的长度。dp
是一个二维数组,用来存储从一个子序列转换到另一个子序列所需的最小编辑步数。
- 边界条件:
-
dp[i][0] = i
表示将一个长度为i
的序列转换为空序列,需要删除i
个字符。dp[0][j] = j
表示将一个空序列转换为长度为j
的序列,需要插入j
个字符。
- 动态规划填充:
-
- 对于每个
dp[i][j]
,如果当前字符相同(dna1.charAt(i - 1) == dna2.charAt(j - 1)
),则不需要额外操作,直接继承上一个状态的值。 - 如果当前字符不同,则取以下三种操作中的最小值加一:
- 对于每个
-
-
- 删除操作:
dp[i - 1][j]
- 插入操作:
dp[i][j - 1]
- 替换操作:
dp[i - 1][j - 1]
- 删除操作:
-
- 返回结果:
-
- 最终的结果存储在
dp[m][n]
中,表示将整个dna1
转换为整个dna2
所需的最小编辑步数。
- 最终的结果存储在
中14数组元素之和最小化
(数学 找规律)
问题描述
小C希望构造一个包含n个元素的数组,且满足以下条件:
- 数组中的所有元素两两不同。
- 数组所有元素的最大公约数为
k
。 - 数组元素之和尽可能小。
任务是输出该数组元素之和的最小值。
测试样例
样例1:
输入:n = 3 ,k = 1
输出:6
样例2:
输入:n = 2 ,k = 2
输出:6
样例3:
输入:n = 4 ,k = 3
输出:30
public class Main {
/**
* 计算能够选出的 n 个不同的正整数,使得这些数的最大公约数(GCD)恰好为 k 的数列之和。
*
* @param n 正整数的数量
* @param k 所选数的最大公约数
* @return 数列之和
*/
public static int solution(int n, int k) {
// 特殊情况处理:如果 k 为 1
if (k == 1) {
// 当 k 为 1 时,可以选择最小的 n 个不同的正整数,即 1, 2, 3, ..., n
// 这些数的最大公约数为 1,且它们的和可以用等差数列求和公式计算:
// S = 1 + 2 + 3 + ... + n = n * (n + 1) / 2
return n * (n + 1) / 2;
}
// 一般情况:当 k 不为 1
// 为了确保所选数的最大公约数为 k,可以选择 k, 2k, 3k, ..., nk
// 这些数的最大公约数为 k,且它们的和也可以用等差数列求和公式计算:
// S = k + 2k + 3k + ... + nk = k * (1 + 2 + 3 + ... + n) = k * n * (n + 1) / 2
return k * n * (n + 1) / 2;
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(3, 1)); // 输出: 6,因为选择的数是 1, 2, 3,它们的和为 6
System.out.println(solution(2, 2)); // 输出: 6,因为选择的数是 2, 4,它们的和为 6
System.out.println(solution(4, 3)); // 输出: 30,因为选择的数是 3, 6, 9, 12,它们的和为 30
}
}
详细解析
- 方法签名:
-
public static int solution(int n, int k)
: 定义了一个静态方法solution
,它接受两个整数参数n
和k
,并返回一个整数结果。
- 特殊情况处理:
-
- 当
k
为 1 时,选择的数是 1, 2, 3, ..., n。这些数的最大公约数为 1,且它们的和可以用等差数列求和公式计算:
[
S = 1 + 2 + 3 + \ldots + n = \frac{n(n + 1)}{2}
]
- 当
- 一般情况处理:
-
- 当
k
不为 1 时,选择的数是 k, 2k, 3k, ..., nk。这些数的最大公约数为 k,且它们的和也可以用等差数列求和公式计算:
[
S = k + 2k + 3k + \ldots + nk = k \cdot (1 + 2 + 3 + \ldots + n) = k \cdot \frac{n(n + 1)}{2}
]
- 当
- 主函数测试案例:
-
solution(3, 1)
: 选择的数是 1, 2, 3,它们的和为 6。solution(2, 2)
: 选择的数是 2, 4,它们的和为 6。solution(4, 3)
: 选择的数是 3, 6, 9, 12,它们的和为 30。
易 20 比赛配对问题
(数学 模拟)
问题描述
小R正在组织一个比赛,比赛中有 n
支队伍参赛。比赛遵循以下独特的赛制:
- 如果当前队伍数为 偶数,那么每支队伍都会与另一支队伍配对。总共进行
n / 2
场比赛,且产生n / 2
支队伍进入下一轮。 - 如果当前队伍数为 奇数,那么将会随机轮空并晋级一支队伍,其余的队伍配对。总共进行
(n - 1) / 2
场比赛,且产生(n - 1) / 2 + 1
支队伍进入下一轮。
小R想知道在比赛中进行的配对次数,直到决出唯一的获胜队伍为止。
测试样例
样例1:
输入:n = 7
输出:6
样例2:
输入:n = 14
输出:13
样例3:
输入:n = 1
输出:0
public class Main {
/**
* 计算在一个淘汰赛中需要进行的比赛场次。
* 比赛规则如下:
* - 如果队伍数是偶数,每两支队伍之间进行一场比赛,胜者进入下一轮。
* - 如果队伍数是奇数,除了一组队伍轮空外,其余的每两支队伍之间也进行一场比赛,胜者加上轮空的队伍一起进入下一轮。
* - 比赛一直进行,直到只剩下一支队伍。
*
* @param n 初始的队伍数量
* @return 总的比赛场次
*/
public static int solution(int n) {
int matches = 0; // 初始化配对次数为0
// 当队伍数大于1时,继续比赛
while (n > 1) {
if (n % 2 == 0) { // 如果队伍数是偶数
// 进行 n / 2 场比赛
matches += n / 2;
// 剩下 n / 2 支队伍
n /= 2;
} else { // 如果队伍数是奇数
// 进行 (n - 1) / 2 场比赛
matches += (n - 1) / 2;
// 剩下 (n - 1) / 2 + 1 支队伍
n = (n - 1) / 2 + 1;
}
}
return matches; // 返回总的配对次数
}
public static void main(String[] args) {
}
}
详细解析
- 方法
solution
:
-
- 参数:
int n
表示初始的队伍数量。 - 返回值:
int
表示总共需要进行的比赛场次。 - 逻辑:
- 参数:
-
-
- 初始化
matches
变量为0,用于记录比赛场次。 - 使用
while
循环,当队伍数量n
大于1时,继续比赛。 - 在循环中,检查队伍数量
n
是否为偶数:
- 初始化
-
-
-
-
- 如果是偶数,进行
n / 2
场比赛,每场比赛淘汰一支队伍,剩下的队伍数为n / 2
。 - 如果是奇数,进行
(n - 1) / 2
场比赛,每场比赛淘汰一支队伍,剩下的队伍数为(n - 1) / 2 + 1
(其中一支队伍轮空)。
- 如果是偶数,进行
-
-
-
-
- 每次比赛后,更新
matches
和n
。 - 当
n
减少到1时,退出循环并返回matches
。
- 每次比赛后,更新
-
- 方法
main
:
-
- 功能: 测试
solution
方法并打印结果。 - 测试用例:
- 功能: 测试
-
-
solution(7)
应该返回6,因为需要6场比赛才能从7支队伍中决出冠军。solution(14)
应该返回13,因为需要13场比赛才能从14支队伍中决出冠军。solution(1)
应该返回0,因为只有1支队伍,不需要比赛。
-
- 测试样例解析:
-
- 对每个测试样例,详细解释了每一轮比赛的情况,帮助理解算法的工作原理。
易 30组成字符串ku的最大次数
问题描述
给定一个字符串 ss,该字符串中只包含英文大小写字母。你需要计算从字符串中最多能组成多少个字符串 "ku"
。每次可以随机从字符串中选一个字符,并且选中的字符不能再使用。字符串中的字符大小写可以忽略,即大写和小写字母视为相同。
例如,输入 "AUBTMKAxfuu"
,从中最多能组成 1 个 "ku"
。
测试样例
样例1:
输入:s = "AUBTMKAxfuu"
输出:1
样例2:
输入:s = "KKuuUuUuKKKKkkkkKK"
输出:6
样例3:
输入:s = "abcdefgh"
输出:0
public class Main {
/**
* 计算可以从给定字符串中组成 "ku" 字符串的最大数量。
*
* @param s 输入的字符串
* @return 可以组成的 "ku" 字符串的最大数量
*/
public static int solution(String s) {
// 初始化两个计数器,分别用于统计字符 'k' 和 'u' 的数量
int countK = 0;
int countU = 0;
// 遍历字符串中的每一个字符
for (char c : s.toCharArray()) {
// 将字符转换为小写,以便不区分大小写地进行比较
char lowerC = Character.toLowerCase(c);
// 如果字符是 'k',则增加 'k' 的计数
if (lowerC == 'k') {
countK++;
}
// 如果字符是 'u',则增加 'u' 的计数
else if (lowerC == 'u') {
countU++;
}
}
// 组成 "ku" 字符串的数量取决于 'k' 和 'u' 中数量较少的那个,
// 因为每个 "ku" 需要一个 'k' 和一个 'u'
return Math.min(countK, countU);
}
/**
* 主方法,用于运行测试用例。
*
* @param args 命令行参数(本例中未使用)
*/
public static void main(String[] args) {
// 测试用例 1
System.out.println(solution("AUBTMKAxfuu") == 1); // 输出: true
// 解析:有一个 'k' 和两个 'u',所以可以组成一个 "ku"
// 测试用例 2
System.out.println(solution("KKuuUuUuKKKKkkkkKK") == 6); // 输出: true
// 解析:有八个 'k' 和六个 'u',所以可以组成六个 "ku"
// 测试用例 3
System.out.println(solution("abcdefgh") == 0); // 输出: true
// 解析:没有 'k' 或 'u',所以不能组成任何 "ku"
}
}
详细解析
- 方法
solution
:
-
- 参数
s
是一个字符串,表示输入的文本。 - 方法内部定义了两个计数器
countK
和countU
,分别用于记录字符 'k' 和 'u' 的出现次数。 - 使用
for
循环遍历字符串中的每一个字符,将每个字符转换为小写后进行比较。 - 如果字符是 'k',则增加
countK
;如果字符是 'u',则增加countU
。 - 最后,使用
Math.min(countK, countU)
返回两个计数器中较小的一个值,这是因为组成一个 "ku" 需要一个 'k' 和一个 'u'。
- 参数
- 主方法
main
:
-
- 这个方法用于运行几个测试用例,验证
solution
方法的正确性。 - 每个测试用例都调用
solution
方法,并将结果与预期值进行比较,输出true
或false
。 - 通过这些测试用例,可以确保方法在不同情况下都能正确工作。
- 这个方法用于运行几个测试用例,验证
(动态规划 DP)难 33卡牌翻面求和问题
(动态规划 DP)
问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2)
,(2,3)
和 (3,2)
,你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例1:
输入:n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]
输出:3
样例2:
输入:n = 4 ,a = [3, 1, 2, 4] ,b = [1, 2, 3, 1]
输出:6
样例3:
输入:n = 5 ,a = [1, 2, 3, 4, 5] ,b = [1, 2, 3, 4, 5]
输出:32
public class Main {
public static final int MOD = 1000000007; // 定义取模的常量
/**
* 计算从前 n 张卡牌中选择任意一面,使得总和模 3 余 0 的方案数。
*
* @param n 卡牌的数量
* @param a 每张卡牌的正面数字数组
* @param b 每张卡牌的反面数字数组
* @return 满足条件的方案数
*/
public static int solution(int n, int[] a, int[] b) {
// dp[i][j] 表示前 i 张卡牌,使得总和模 3 余 j 的方案数
int[][] dp = new int[n + 1][3];
// 初始状态:0 张卡牌时,模 3 余 0 的方案数为 1,其他为 0
dp[0][0] = 1;
dp[0][1] = 0;
dp[0][2] = 0;
// 遍历每一张卡牌
for (int i = 1; i <= n; i++) {
// 当前卡牌的正面和反面数字
int num1 = a[i - 1];
int num2 = b[i - 1];
// 遍历模 3 的余数
for (int j = 0; j < 3; j++) {
// 选择正面的情况
dp[i][(j + num1) % 3] = (dp[i][(j + num1) % 3] + dp[i - 1][j]) % MOD;
// 选择反面的情况
dp[i][(j + num2) % 3] = (dp[i][(j + num2) % 3] + dp[i - 1][j]) % MOD;
}
}
// 返回前 n 张卡牌,使得总和模 3 余 0 的方案数
return dp[n][0];
}
/**
* 主方法,用于运行测试用例。
*
* @param args 命令行参数(本例中未使用)
*/
public static void main(String[] args) {
// 测试用例 1
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{2, 3, 2}) == 3); // 输出: true
// 解析:可以通过以下组合使总和模 3 余 0:
// - [1, 2, 2]
// - [1, 3, 2]
// - [2, 2, 2]
// 测试用例 2
System.out.println(solution(4, new int[]{3, 1, 2, 4}, new int[]{1, 2, 3, 1}) == 6); // 输出: true
// 解析:可以通过以下组合使总和模 3 余 0:
// - [3, 1, 2, 1]
// - [3, 1, 3, 1]
// - [3, 2, 2, 1]
// - [3, 2, 3, 1]
// - [1, 1, 2, 1]
// - [1, 2, 2, 1]
// 测试用例 3
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 32); // 输出: true
// 解析:可以通过多种组合使总和模 3 余 0,具体组合较多,这里不再列出。
}
}
易 34游戏排名第三大的分数
问题描述
小M想要通过查看往届游戏比赛的排名来确定自己比赛的目标分数。他希望找到往届比赛中排名第三的分数,作为自己的目标。具体规则如下:
- 如果分数中有三个或以上不同的分数,返回其中第三大的分数。
- 如果不同的分数只有两个或更少,那么小M将选择最大的分数作为他的目标。
请你帮小M根据给定的分数数组计算目标分数。
测试样例
样例1:
输入:n = 3,nums = [3, 2, 1]
输出:1
样例2:
输入:n = 2,nums = [1, 2]
输出:2
样例3:
输入:n = 4,nums = [2, 2, 3, 1]
输出:1
set自动去重
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static int solution(int n, int[] nums) {
// 使用Set来存储不同的分数,Set会自动去重
Set<Integer> uniqueScores = new HashSet<>();
// 将所有分数添加到Set中
for (int score : nums) {
uniqueScores.add(score);
}
// 将Set转换为数组,并进行排序
Integer[] sortedScores = uniqueScores.toArray(new Integer[0]);
Arrays.sort(sortedScores);
// 如果不同的分数数量大于等于3,返回第三大的分数
if (sortedScores.length >= 3) {
return sortedScores[sortedScores.length - 3];
} else {
// 否则返回最大的分数
return sortedScores[sortedScores.length - 1];
}
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(3, new int[]{3, 2, 1}) == 1); // 输出: true
System.out.println(solution(2, new int[]{1, 2}) == 2); // 输出: true
System.out.println(solution(4, new int[]{2, 2, 3, 1}) == 1); // 输出: true
}
}
数组和排序
import java.util.Arrays;
public class Main {
public static int solution(int n, int[] nums) {
// 先对数组进行排序
Arrays.sort(nums);
// 初始化一个变量来记录不同的分数数量
int uniqueCount = 0;
// 从后向前遍历数组,寻找不同的分数
int lastUniqueScore = Integer.MIN_VALUE;
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] != lastUniqueScore) {
lastUniqueScore = nums[i];
uniqueCount++;
// 如果找到了第三个不同的分数,直接返回
if (uniqueCount == 3) {
return lastUniqueScore;
}
}
}
// 如果没有找到第三个不同的分数,返回最大的分数
return lastUniqueScore;
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(3, new int[]{3, 2, 1})); // 输出: 1
System.out.println(solution(2, new int[]{1, 2})); // 输出: 2
System.out.println(solution(4, new int[]{2, 2, 3, 1})); // 输出: 1
}
}
直接读取整个数组
import java.util.Arrays;
public class Main {
public static int solution(int n, int[] nums) {
// 先对数组进行排序
Arrays.sort(nums);
// 初始化一个变量来记录不同的分数数量
int uniqueCount = 0;
// 从后向前遍历数组,寻找不同的分数
int lastUniqueScore = Integer.MIN_VALUE;
for (int i = nums.length - 1; i >= 0; i--) {
if (nums[i] != lastUniqueScore) {
lastUniqueScore = nums[i];
uniqueCount++;
// 如果找到了第三个不同的分数,直接返回
if (uniqueCount == 3) {
return lastUniqueScore;
}
}
}
// 如果没有找到第三个不同的分数,返回最大的分数
return nums[nums.length - 1];
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(3, new int[]{3, 2, 1}) == 1); // 输出: true
System.out.println(solution(2, new int[]{1, 2}) == 2); // 输出: true
System.out.println(solution(4, new int[]{2, 2, 3, 1}) == 1); // 输出: true
}
}
易 47完美偶数计数
问题描述
小C定义了一个“完美偶数”。一个正整数 xx 被认为是完美偶数需要满足以下两个条件:
- xx 是偶数;
- xx 的值在区间 [l,r][l,r] 之间。
现在,小C有一个长度为 nn 的数组 aa,她想知道在这个数组中有多少个完美偶数。
测试样例
样例1:
输入:n = 5,l = 3,r = 8,a = [1, 2, 6, 8, 7]
输出:2
样例2:
输入:n = 4,l = 10,r = 20,a = [12, 15, 18, 9]
输出:2
样例3:
输入:n = 3,l = 1,r = 10,a = [2, 4, 6]
输出:3
import java.util.Arrays;
public class Main {
/**
* 计算数组中完美偶数的数量
*
* @param n 数组的长度
* @param l 完美偶数的下限
* @param r 完美偶数的上限
* @param a 数组
* @return 完美偶数的数量
*/
public static int solution(int n, int l, int r, int[] a) {
int perfectEvenCount = 0; // 初始化完美偶数的数量
// 遍历数组中的每一个元素
for (int num : a) {
// 检查当前元素是否是偶数,并且是否在区间 [l, r] 之间
if (num % 2 == 0 && num >= l && num <= r) {
perfectEvenCount++; // 如果是完美偶数,数量加一
}
}
return perfectEvenCount; // 返回完美偶数的数量
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(5, 3, 8, new int[]{1, 2, 6, 8, 7})); // 输出: 2
// 测试样例2
System.out.println(solution(4, 10, 20, new int[]{12, 15, 18, 9})); // 输出: 2
// 测试样例3
System.out.println(solution(3, 1, 10, new int[]{2, 4, 6})); // 输出: 3
}
}
中 60 RGB色值转换为整数值
问题描述
小M需要一个函数,用于将RGB颜色值转换为相应的十六进制整数值。RGB色值以字符串的形式给出,如"rgb(192, 192, 192)"
,需要转换为对应的整数值。
测试样例
样例1:
输入:rgb = "rgb(192, 192, 192)"
输出:12632256
样例2:
输入:rgb = "rgb(100, 0, 252)"
输出:6553852
样例3:
输入:rgb = "rgb(33, 44, 55)"
输出:2174007
样例4:
输入:rgb = "rgb(255, 255, 255)"
输出:16777215
样例5:
输入:rgb = "rgb(0, 0, 0)"
输出:0
字符串操作和位移操作符
public class Main {
/**
* 将给定的 RGB 字符串转换为十六进制整数值。
*
* @param rgb 输入的 RGB 字符串,格式为 "rgb(R, G, B)"
* @return 对应的十六进制整数值
*/
public static int solution(String rgb) {
// 1. 使用正则表达式和字符串操作去除多余的字符
// 将 "rgb(R, G, B)" 转换为 "R, G, B"
String cleanedString = rgb.replace("rgb(", "").replace(")", "");
// 2. 使用逗号分隔字符串,得到 R、G、B 的字符串数组
String[] parts = cleanedString.split(",");
// 3. 将字符串数组中的每个元素转换为整数
int r = Integer.parseInt(parts[0].trim()); // 红色值
int g = Integer.parseInt(parts[1].trim()); // 绿色值
int b = Integer.parseInt(parts[2].trim()); // 蓝色值
// 4. 将 R、G、B 值转换为十六进制整数值
// 计算公式为:(r << 16) | (g << 8) | b
// 其中 << 是位移操作符,将 r 左移 16 位,g 左移 8 位,b 不移位
int hexValue = (r << 16) | (g << 8) | b;
return hexValue;
}
/**
* 主方法,用于测试 solution 方法。
*
* @param args 命令行参数(未使用)
*/
public static void main(String[] args) {
// 测试用例
System.out.println(solution("rgb(192, 192, 192)") == 12632256); // 输出 true
System.out.println(solution("rgb(100, 0, 252)") == 6553852); // 输出 true
System.out.println(solution("rgb(33, 44, 55)") == 2174007); // 输出 true
System.out.println(solution("rgb(255, 255, 255)") == 16777215); // 输出 true
System.out.println(solution("rgb(0, 0, 0)") == 0); // 输出 true
}
}
详细解析
solution
方法
- 字符串清理:
-
String cleanedString = rgb.replace("rgb(", "").replace(")", "");
-
-
- 这一步通过两次
replace
操作去除了字符串开头的"rgb("
和结尾的")"
,使得字符串变为"R, G, B"
的形式。
- 这一步通过两次
-
- 字符串分割:
-
String[] parts = cleanedString.split(",");
-
-
- 使用
split
方法以逗号,
作为分隔符,将字符串分割成一个字符串数组parts
,其中parts[0]
是红色值,parts[1]
是绿色值,parts[2]
是蓝色值。
- 使用
-
- 字符串转整数:
-
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
-
-
- 使用
Integer.parseInt
方法将字符串数组中的每个元素转换为整数。trim
方法用于去除可能存在的空格。
- 使用
-
- 计算十六进制值:
-
int hexValue = (r << 16) | (g << 8) | b;
-
-
- 使用位移操作符
<<
将红色值r
左移 16 位,绿色值g
左移 8 位,蓝色值b
不移位。然后通过按位或操作符|
将它们组合成一个十六进制整数值。
- 使用位移操作符
-
main
方法
main
方法用于测试solution
方法的正确性。- 每个测试用例调用
solution
方法,并检查返回值是否与预期相符。 - 如果返回值与预期相符,则输出
true
;否则输出false
。
测试案例分析
"rgb(192, 192, 192)"
应返回12632256
,这是灰色的十六进制值。"rgb(100, 0, 252)"
应返回6553852
,这是一个接近纯蓝的颜色。"rgb(33, 44, 55)"
应返回2174007
,这是一种暗色调。"rgb(255, 255, 255)"
应返回16777215
,这是白色的十六进制值。"rgb(0, 0, 0)"
应返回0
,这是黑色的十六进制值。
Java的内置方法 提取正则表达式
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
/**
* 将RGB字符串转换为对应的十六进制整数值。
*
* @param rgb RGB字符串,格式如 "rgb(192, 192, 192)"
* @return 对应的十六进制整数值
*/
public static int solution(String rgb) {
// 定义正则表达式来匹配RGB值中的数字
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(rgb);
// 初始化RGB值
int r = 0, g = 0, b = 0;
// 使用Matcher对象提取RGB值
if (matcher.find()) {
r = Integer.parseInt(matcher.group()); // 提取红色值
}
if (matcher.find()) {
g = Integer.parseInt(matcher.group()); // 提取绿色值
}
if (matcher.find()) {
b = Integer.parseInt(matcher.group()); // 提取蓝色值
}
// 计算十六进制值
int hexValue = (r << 16) | (g << 8) | b; // 将RGB值组合成一个整数
return hexValue;
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution("rgb(192, 192, 192)") == 12632256); // true
System.out.println(solution("rgb(100, 0, 252)") == 6553852); // true
System.out.println(solution("rgb(33, 44, 55)") == 2174007); // true
System.out.println(solution("rgb(255, 255, 255)") == 16777215); // true
System.out.println(solution("rgb(0, 0, 0)") == 0); // true
}
}
(动态规划)中 80 股票市场交易策略优化
问题描述
小R近期表现出色,公司决定以股票的形式给予奖励,并允许他在市场上进行交易以最大化收益。给定一个数组,数组中的第 i
个元素代表第 i
天的股票价格。小R需要设计一个算法来实现最大利润。
股票交易规则如下:
- 小R可以多次买卖股票,但在买入新的股票前必须卖出之前的股票。
- 每次卖出股票后存在一天的冷冻期,在冷冻期内小R不能购买股票。
你的任务是帮助小R计算出在遵守交易规则的情况下能够获得的最大利润。
stocks
: 一个整数列表,表示连续几天内的股票价格。
测试样例
样例1:
输入:stocks = [1, 2]
输出:1
样例2:
输入:stocks = [2, 1]
输出:0
样例3:
输入:stocks = [1, 2, 3, 0, 2]
输出:3
样例4:
输入:stocks = [2, 3, 4, 5, 6, 7]
输出:5
样例5:
输入:stocks = [1, 6, 2, 7, 13, 2, 8]
输出:12
public class Main {
/**
* 计算在给定股票价格数组中,按照特定规则(买入、卖出、冷冻期)能获得的最大利润。
*
* @param stocks 股票价格数组
* @return 最大利润
*/
public static int solution(int[] stocks) {
// 如果股票价格数组为空或只有一个价格,无法进行交易,直接返回0
if (stocks == null || stocks.length <= 1) {
return 0;
}
// 定义三个状态:
// buy[i]: 第i天买入股票后的最大利润
// sell[i]: 第i天卖出股票后的最大利润
// cooldown[i]: 第i天处于冷冻期的最大利润
int n = stocks.length;
int[] buy = new int[n];
int[] sell = new int[n];
int[] cooldown = new int[n];
// 初始化第一天的情况
// 第一天买入股票,利润为 -stocks[0](因为买入需要花费)
buy[0] = -stocks[0];
// 第一天无法卖出股票,利润为 0
sell[0] = 0;
// 第一天无法处于冷冻期,利润为 0
cooldown[0] = 0;
// 从第二天开始遍历每一天
for (int i = 1; i < n; i++) {
// 第i天买入股票的最大利润:
// 1. 前一天处于冷冻期,今天买入股票(前一天冷冻期的利润 - 今天买入的股票价格)
// 2. 前一天已经持有股票,今天继续持有(前一天买入的利润)
buy[i] = Math.max(cooldown[i - 1] - stocks[i], buy[i - 1]);
// 第i天卖出股票的最大利润:
// 1. 前一天持有股票,今天卖出股票(前一天买入的利润 + 今天卖出的股票价格)
sell[i] = buy[i - 1] + stocks[i];
// 第i天处于冷冻期的最大利润:
// 1. 前一天卖出股票,今天处于冷冻期(前一天卖出的利润)
// 2. 前一天已经处于冷冻期(前一天冷冻期的利润)
cooldown[i] = Math.max(sell[i - 1], cooldown[i - 1]);
}
// 最终的最大利润应该是最后一天卖出股票或处于冷冻期的最大值
// 因为我们希望在最后一天结束时手中没有股票
return Math.max(sell[n - 1], cooldown[n - 1]);
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new int[]{1, 2}) == 1); // 输出 true
System.out.println(solution(new int[]{2, 1}) == 0); // 输出 true
System.out.println(solution(new int[]{1, 2, 3, 0, 2}) == 3); // 输出 true
System.out.println(solution(new int[]{2, 3, 4, 5, 6, 7}) == 5); // 输出 true
System.out.println(solution(new int[]{1, 6, 2, 7, 13, 2, 8}) == 12); // 输出 true
}
}
详细思路解析
- 问题定义:
-
- 给定一个数组
stocks
,其中每个元素表示某一天的股票价格。 - 目标是在遵循以下规则的情况下,计算能获得的最大利润:
- 给定一个数组
-
-
- 每天可以选择买入、卖出或不操作。
- 卖出股票后,下一次买入前必须至少过一天(即存在一个冷冻期)。
-
- 动态规划状态定义:
-
buy[i]
:第i
天结束时持有股票的最大利润。sell[i]
:第i
天结束时刚卖出股票的最大利润。cooldown[i]
:第i
天结束时处于冷冻期的最大利润。
- 初始条件:
-
- 第一天买入股票,利润为
-stocks[0]
。 - 第一天无法卖出股票,利润为
0
。 - 第一天无法处于冷冻期,利润为
0
。
- 第一天买入股票,利润为
- 状态转移方程:
-
buy[i] = max(cooldown[i - 1] - stocks[i], buy[i - 1])
:
-
-
- 前一天处于冷冻期,今天买入股票。
- 前一天已经持有股票,今天继续持有。
-
-
sell[i] = buy[i - 1] + stocks[i]
:
-
-
- 前一天持有股票,今天卖出股票。
-
-
cooldown[i] = max(sell[i - 1], cooldown[i - 1])
:
-
-
- 前一天卖出股票,今天处于冷冻期。
- 前一天已经处于冷冻期。
-
- 最终结果:
-
- 最终的最大利润应该是最后一天卖出股票或处于冷冻期的最大值,因为最后一天结束时,我们希望手中没有股票以获取最大利润。
中 81 古生物DNA序列血缘分析
问题描述
小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。
你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。
dna1
: 第一条DNA序列。dna2
: 第二条DNA序列。
测试样例
样例1:
输入:dna1 = "AGT",dna2 = "AGCT"
输出:1
样例2:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
样例3:
输入:dna1 = "ACGT",dna2 = "TGC"
输出:3
样例4:
输入:dna1 = "A",dna2 = "T"
输出:1
样例5:
输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4
public class Main {
/**
* 计算两个DNA序列之间的最小编辑距离。
*
* @param dna1 第一个DNA序列
* @param dna2 第二个DNA序列
* @return 两个DNA序列之间的最小编辑距离
*/
public static int solution(String dna1, String dna2) {
int m = dna1.length(); // 获取第一个DNA序列的长度
int n = dna2.length(); // 获取第二个DNA序列的长度
// 创建一个二维数组dp,用于存储子问题的解
// dp[i][j] 表示dna1的前i个字符和dna2的前j个字符之间的最小编辑距离
int[][] dp = new int[m + 1][n + 1];
// 初始化dp数组的第一行和第一列
// 这里表示一个序列为空的情况下,另一个序列转为空序列所需的操作数
for (int i = 0; i <= m; i++) {
dp[i][0] = i; // 将dna1的前i个字符转换为空字符串,需要i次删除操作
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j; // 将空字符串转换为dna2的前j个字符,需要j次插入操作
}
// 动态规划的核心部分,填充dp数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果当前字符相同,那么它们之间的转换不需要任何操作
if (dna1.charAt(i - 1) == dna2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]; // 直接继承前一状态的值
} else {
// 如果当前字符不同,则考虑以下三种操作,并选择其中代价最小的一种:
// 1. 插入操作:在dna1的第i个位置插入一个字符使其与dna2的第j个字符相同
// 2. 删除操作:从dna1中删除第i个字符使其与dna2的前j个字符匹配
// 3. 替换操作:将dna1的第i个字符替换成dna2的第j个字符
dp[i][j] = Math.min(dp[i][j - 1] + 1, // 插入操作
Math.min(dp[i - 1][j] + 1, // 删除操作
dp[i - 1][j - 1] + 1)); // 替换操作
}
}
}
// dp[m][n] 存储的是整个dna1和dna2之间的最小编辑距离
return dp[m][n];
}
public static void main(String[] args) {
// 测试用例,用于验证solution方法的正确性
System.out.println(solution("AGT", "AGCT") == 1); // 输出: true,仅需一次插入操作
System.out.println(solution("AACCGGTT", "AACCTTGG") == 4); // 输出: true,需要四次替换操作
System.out.println(solution("ACGT", "TGC") == 3); // 输出: true,需要三次操作(两次替换一次删除)
System.out.println(solution("A", "T") == 1); // 输出: true,只需一次替换操作
System.out.println(solution("GGGG", "TTTT") == 4); // 输出: true,每个字符都需要替换
}
}
思路解析
- 问题定义:
-
- 编辑距离是指将一个字符串转换成另一个字符串所需要的最少操作次数,这些操作包括插入一个字符、删除一个字符或替换一个字符。
- 动态规划模型构建:
-
- 我们使用一个二维数组
dp
来记录子问题的解,其中dp[i][j]
表示dna1
的前i
个字符和dna2
的前j
个字符之间的最小编辑距离。 - 初始条件是当其中一个字符串为空时,编辑距离等于另一个字符串的长度,因为可以通过相应数量的插入或删除操作将其转换为空字符串。
- 我们使用一个二维数组
- 状态转移方程:
-
- 对于
dp[i][j]
的计算,我们考虑两种情况:
- 对于
-
-
- 如果
dna1
的第i
个字符和dna2
的第j
个字符相同,那么不需要进行任何操作,dp[i][j]
的值就等于dp[i-1][j-1]
。 - 如果字符不同,则需要进行操作(插入、删除或替换),此时
dp[i][j]
的值就是这三种操作中代价最小的那个加上1。
- 如果
-
- 最终结果:
-
dp[m][n]
即为dna1
和dna2
之间的最小编辑距离。
这个算法的时间复杂度为 O(mn),空间复杂度也为 O(mn),其中 m
和 n
分别是两个字符串的长度。这种动态规划的方法非常适合解决这类字符串比较的问题。
中 82 理想火车站定位
问题描述
小F是A市的市长,正在计划在A市新建一个火车站以方便市民的日常出行。市区内的街道布局十分规整,形成网格状。从一个位置[x1, y1]
到另一个位置[x2, y2]
的距离计算方法为 |x1 - x2| + |y1 - y2|
,即曼哈顿距离。
在初步考察后,市政府列出了M
个可能的火车站建设点。为了使得市民到火车站的总旅行时间最短,小F希望选出一个最优位置作为火车站的地址。
请你帮助小F计算出哪一个位置最适合建设新火车站。
N
: 市民的总人数。M
: 可建设火车站的备选位置数。citizens
: 一个列表,每个元素是一个元组[x_i, y_i]
,表示第i
位市民的居住位置。locations
: 一个列表,每个元素是一个元组[p_i, q_i]
,表示第i
个备选的火车站位置。
如果有多个火车站最优,那么选择第一次出现的那个。
测试样例
样例1:
输入:n = 4,m = 3,citizens = [[-1, -1], [-1, 1], [1, -1], [1, 1]],locations = [[3, 2], [1, 0], [0, 0]]
输出:[1, 0]
样例2:
输入:n = 2,m = 2,citizens = [[0, 0], [0, 4]],locations = [[0, 2], [0, 3]]
输出:[0, 2]
样例3:
输入:n = 3,m = 1,citizens = [[10, 10], [20, 20], [30, 30]],locations = [[15, 15]]
输出:[15, 15]
样例4:
输入:n = 5,m = 3,citizens = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]],locations = [[4, 5], [6, 7], [8, 9]]
输出:[4, 5]
样例5:
输入:n = 6,m = 2,citizens = [[10, 10], [20, 20], [30, 30], [40, 40], [50, 50], [60, 60]],locations = [[35, 35], [45, 45]]
输出:[35, 35]
public class Main {
/**
* 计算两个点之间的曼哈顿距离。
*
* @param point1 第一个点,包含两个元素 [x1, y1]
* @param point2 第二个点,包含两个元素 [x2, y2]
* @return 曼哈顿距离 |x1 - x2| + |y1 - y2|
*/
public static int manhattanDistance(int[] point1, int[] point2) {
return Math.abs(point1[0] - point2[0]) + Math.abs(point1[1] - point2[1]);
}
/**
* 计算最优火车站位置,使得所有市民到该位置的总曼哈顿距离最小。
*
* @param n 市民数量
* @param m 备选位置数量
* @param citizens 市民的位置列表,每个位置是一个包含两个元素的数组 [x, y]
* @param locations 备选位置列表,每个位置是一个包含两个元素的数组 [x, y]
* @return 最优位置,一个包含两个元素的数组 [x, y]
*/
public static int[] solution(int n, int m, int[][] citizens, int[][] locations) {
// 初始化最小总距离为一个很大的值
int minTotalDistance = Integer.MAX_VALUE;
// 初始化最优位置为 [-1, -1]
int[] bestLocation = new int[]{-1, -1};
// 遍历每一个备选的火车站位置
for (int[] location : locations) {
int totalDistance = 0;
// 计算所有市民到当前备选位置的总距离
for (int[] citizen : citizens) {
totalDistance += manhattanDistance(citizen, location);
}
// 如果当前总距离小于最小总距离,更新最小总距离和最优位置
if (totalDistance < minTotalDistance) {
minTotalDistance = totalDistance;
bestLocation = location;
}
}
// 返回最优位置
return bestLocation;
}
/**
* 辅助方法,用于比较两个数组是否相等。
*
* @param a 第一个数组
* @param b 第二个数组
* @return 如果两个数组相等则返回 true,否则返回 false
*/
public static boolean arrayEqual(int[] a, int[] b) {
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
/**
* 主函数,用于测试。
*/
public static void main(String[] args) {
// 测试样例1
int[][] citizens1 = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
int[][] locations1 = {{3, 2}, {1, 0}, {0, 0}};
int[] result1 = solution(4, 3, citizens1, locations1);
int[] expected1 = {1, 0};
System.out.println(arrayEqual(result1, expected1)); // 输出: true
// 测试样例2
int[][] citizens2 = {{0, 0}, {0, 4}};
int[][] locations2 = {{0, 2}, {0, 3}};
int[] result2 = solution(2, 2, citizens2, locations2);
int[] expected2 = {0, 2};
System.out.println(arrayEqual(result2, expected2)); // 输出: true
// 测试样例3
int[][] citizens3 = {{10, 10}, {20, 20}, {30, 30}};
int[][] locations3 = {{15, 15}};
int[] result3 = solution(3, 1, citizens3, locations3);
int[] expected3 = {15, 15};
System.out.println(arrayEqual(result3, expected3)); // 输出: true
// 测试样例4
int[][] citizens4 = {{1, 2}, {3, 4}, {5, 6}, {7, 8}, {9, 10}};
int[][] locations4 = {{4, 5}, {6, 7}, {8, 9}};
int[] result4 = solution(5, 3, citizens4, locations4);
int[] expected4 = {4, 5};
System.out.println(arrayEqual(result4, expected4)); // 输出: true
// 测试样例5
int[][] citizens5 = {{10, 10}, {20, 20}, {30, 30}, {40, 40}, {50, 50}, {60, 60}};
int[][] locations5 = {{35, 35}, {45, 45}};
int[] result5 = solution(6, 2, citizens5, locations5);
int[] expected5 = {35, 35};
System.out.println(arrayEqual(result5, expected5)); // 输出: true
}
}
详细解析
- manhattanDistance 方法:
-
- 输入: 两个点
point1
和point2
,每个点都是一个包含两个整数的数组,表示点的坐标[x, y]
。 - 输出: 两个点之间的曼哈顿距离,计算公式为
|x1 - x2| + |y1 - y2|
。 - 作用: 用于计算任意两个点之间的距离,这是后续计算总距离的基础。
- 输入: 两个点
- solution 方法:
-
- 输入:
-
-
n
: 市民的数量。m
: 备选位置的数量。citizens
: 市民的位置列表,每个位置是一个包含两个整数的数组[x, y]
。locations
: 备选位置列表,每个位置是一个包含两个整数的数组[x, y]
。
-
-
- 输出: 最优位置,一个包含两个整数的数组
[x, y]
。 - 过程:
- 输出: 最优位置,一个包含两个整数的数组
-
-
- 初始化最小总距离为
Integer.MAX_VALUE
,表示一个非常大的值。 - 初始化最优位置为
[-1, -1]
,表示尚未找到最优位置。 - 遍历每个备选位置,计算所有市民到该位置的总曼哈顿距离。
- 如果当前总距离小于已记录的最小总距离,则更新最小总距离和最优位置。
- 返回最优位置。
- 初始化最小总距离为
-
- arrayEqual 方法:
-
- 输入: 两个整数数组
a
和b
。 - 输出: 如果两个数组相等则返回
true
,否则返回false
。 - 过程:
- 输入: 两个整数数组
-
-
- 首先检查两个数组的长度是否相同,如果不同则直接返回
false
。 - 遍历数组的每个元素,如果发现任何不匹配的元素,则返回
false
。 - 如果所有元素都匹配,则返回
true
。
- 首先检查两个数组的长度是否相同,如果不同则直接返回
-
- main 方法:
-
- 作用: 提供测试用例,验证
solution
方法的正确性。 - 过程:
- 作用: 提供测试用例,验证
-
-
- 定义多个测试用例,每个用例包含市民的位置列表和备选位置列表。
- 调用
solution
方法计算最优位置。 - 使用
arrayEqual
方法检查计算结果是否与预期结果相符,并打印结果。
-
通过这些方法和测试用例,程序能够有效地找到最优的火车站位置,使得所有市民到该位置的总曼哈顿距离最小。
(!反向遍历)不可分割的背包问题 难 83 小S的货船租赁冒险
不可分割的背包问题
问题描述
小S在码头租用货船,有 Q 种不同类型的货船可供选择。每种货船有固定的数量 m[i]
、租赁成本 v[i]
和最大载货量 w[i]
。小S希望在预算 V 元内,租用能够承载最大总货物的货船组合。每种货船的具体信息包括数量、租赁价格和载货量。小S需要你帮忙计算在给定预算下,她能租用的货船的最大总载货量是多少。
Q
: 货船的种类数量。V
: 李华可用的总预算(单位:元)。ships
: 一个列表,其中每个元素是一个元组[m[i], v[i], w[i]]
,分别表示第i
种货船的数量、租赁价格和每艘货船的最大载货量。
测试样例
样例1:
输入:Q = 2,V = 10,ships = [[2, 3, 2], [3, 2, 10]]
输出:32
样例2:
输入:Q = 3,V = 50,ships = [[5, 10, 20], [2, 20, 30], [3, 15, 25]]
输出:100
样例3:
输入:Q = 1,V = 100,ships = [[10, 5, 50]]
输出:500
样例4:
输入:Q = 4,V = 100,ships = [[1, 100, 200], [2, 50, 100], [3, 33, 66], [4, 25, 50]]
输出:200
样例5:
输入:Q = 2,V = 300,ships = [[100, 1, 1], [50, 5, 10]]
输出:550
动态规划
import java.util.ArrayList;
import java.util.List;
public class Main {
/**
* 动态规划求解在给定预算下最大载货量的问题。
*
* @param Q 不使用的参数,这里可能是为了兼容其他接口而保留的。
* @param V 总预算。
* @param ships 每种货船的信息,包含三个整数:货船数量、租赁成本和最大载货量。
* @return 在给定预算下的最大载货量。
*/
public static int solution(int Q, int V, List<List<Integer>> ships) {
// 创建一个数组来存储在不同预算下的最大载货量
int[] dp = new int[V + 1];
// 遍历每一种类型的货船
for (List<Integer> ship : ships) {
int m = ship.get(0); // 该类型货船的数量
int v = ship.get(1); // 该类型货船的租赁成本
int w = ship.get(2); // 该类型货船的最大载货量
// 使用反向遍历来避免重复计算
for (int j = V; j >= v; j--) {
// 尝试租用不同数量的该类型货船
for (int k = 1; k <= m && k * v <= j; k++) {
// 更新当前预算下的最大载货量
dp[j] = Math.max(dp[j], dp[j - k * v] + k * w);
}
}
}
// 返回在预算V下的最大载货量
return dp[V];
}
public static void main(String[] args) {
// 测试样例1
List<List<Integer>> ships = new ArrayList<>();
ships.add(List.of(2, 3, 2)); // 2艘成本为3的货船,每艘最大载货量为2
ships.add(List.of(3, 2, 10)); // 3艘成本为2的货船,每艘最大载货量为10
System.out.println(solution(2, 10, ships) == 32); // 输出: true
// 测试样例2
List<List<Integer>> ships2 = new ArrayList<>();
ships2.add(List.of(30, 141, 47)); // 30艘成本为141的货船,每艘最大载货量为47
ships2.add(List.of(9, 258, 12)); // 9艘成本为258的货船,每艘最大载货量为12
ships2.add(List.of(81, 149, 13)); // 81艘成本为149的货船,每艘最大载货量为13
ships2.add(List.of(91, 236, 6)); // 91艘成本为236的货船,每艘最大载货量为6
ships2.add(List.of(27, 163, 74)); // 27艘成本为163的货船,每艘最大载货量为74
ships2.add(List.of(34, 13, 58)); // 34艘成本为13的货船,每艘最大载货量为58
ships2.add(List.of(61, 162, 1)); // 61艘成本为162的货船,每艘最大载货量为1
ships2.add(List.of(80, 238, 29)); // 80艘成本为238的货船,每艘最大载货量为29
ships2.add(List.of(36, 264, 28)); // 36艘成本为264的货船,每艘最大载货量为28
ships2.add(List.of(36, 250, 2)); // 36艘成本为250的货船,每艘最大载货量为2
ships2.add(List.of(70, 214, 31)); // 70艘成本为214的货船,每艘最大载货量为31
ships2.add(List.of(39, 116, 39)); // 39艘成本为116的货船,每艘最大载货量为39
ships2.add(List.of(83, 287, 4)); // 83艘成本为287的货船,每艘最大载货量为4
ships2.add(List.of(61, 269, 94)); // 61艘成本为269的货船,每艘最大载货量为94
ships2.add(List.of(23, 187, 46)); // 23艘成本为187的货船,每艘最大载货量为46
ships2.add(List.of(78, 33, 29)); // 78艘成本为33的货船,每艘最大载货量为29
ships2.add(List.of(46, 151, 2)); // 46艘成本为151的货船,每艘最大载货量为2
ships2.add(List.of(71, 249, 1)); // 71艘成本为249的货船,每艘最大载货量为1
ships2.add(List.of(67, 76, 85)); // 67艘成本为76的货船,每艘最大载货量为85
ships2.add(List.of(72, 239, 17)); // 72艘成本为239的货船,每艘最大载货量为17
ships2.add(List.of(61, 256, 49)); // 61艘成本为256的货船,每艘最大载货量为49
ships2.add(List.of(48, 216, 73)); // 48艘成本为216的货船,每艘最大载货量为73
ships2.add(List.of(39, 49, 74)); // 39艘成本为49的货船,每艘最大载货量为74
System.out.println(solution(23, 400, ships2) == 1740); // 输出: true
}
}
详细解析
- 动态规划数组
dp
:
-
dp[j]
表示在预算为j
的情况下能够达到的最大载货量。- 初始化时,所有元素都设为0,表示初始状态下没有任何货船被租赁。
- 遍历货船信息:
-
- 对于每一种类型的货船,从提供的列表
ships
中获取其数量m
、租赁成本v
和最大载货量w
。
- 对于每一种类型的货船,从提供的列表
- 反向遍历更新动态规划数组:
-
- 为了避免在一次迭代中重复使用同一类型的货船,对预算
V
进行反向遍历。 - 对于每一个可能的预算值
j
,尝试租用1
到m
艘该类型的货船(同时确保不超出当前预算j
)。 - 如果租用
k
艘货船,则更新dp[j]
的值为dp[j - k * v] + k * w
和当前dp[j]
值中的较大者。
- 为了避免在一次迭代中重复使用同一类型的货船,对预算
- 返回结果:
-
- 最终,
dp[V]
即为在预算V
下能够达到的最大载货量。
- 最终,
测试样例分析
- 测试样例1:
-
- 输入:
Q=2
,V=10
,ships=[(2, 3, 2), (3, 2, 10)]
- 输出:
32
- 分析:最佳策略是租用全部3艘成本为2的货船,因为它们的载货量最高且总成本不会超过预算。因此,最大载货量为
3 * 10 = 30
。此外,还可以租用一艘成本为3的货船,这样总成本为3 * 2 + 3 = 9
,仍然在预算内,总载货量为30 + 2 = 32
。
- 输入:
- 测试样例2:
-
- 输入:
Q=23
,V=400
,ships
包含多组数据 - 输出:
1740
- 分析:这个样例更加复杂,涉及多种货船的选择。通过动态规划算法,程序将计算出在不超过预算400的情况下,能够实现的最大载货量为1740。
- 输入:
反向遍历
当然,反向遍历是解决这种多物品选择问题(如背包问题)时的一个关键技巧。它主要用于避免在一次迭代中重复使用同一个物品。让我们详细讲解一下这个过程。
反向遍历的目的
在动态规划中,如果我们正向遍历(从低预算到高预算),可能会导致同一个物品被多次选择,从而违反题目要求。例如,在0-1背包问题中,每个物品只能选择一次,而在多重背包问题中,每个物品可以选多次但有数量限制。
具体步骤
假设我们有一个动态规划数组 dp
,其中 dp[j]
表示在预算为 j
的情况下能够达到的最大载货量。我们需要遍历每一种类型的货船,并更新 dp
数组。
正向遍历的问题
如果我们在正向遍历时更新 dp
数组,可能会出现以下问题:
for (int j = 0; j <= V; j++) {
for (int k = 1; k <= m && k * v <= j; k++) {
dp[j] = Math.max(dp[j], dp[j - k * v] + k * w);
}
}
假设我们有以下数据:
- 货船1:数量
m = 2
,成本v = 3
,载货量w = 2
- 预算
V = 10
在正向遍历时,当 j = 3
时,我们可以选择1艘货船1,更新 dp[3]
:
dp[3] = Math.max(dp[3], dp[0] + 1 * 2) = Math.max(0, 2) = 2
当 j = 6
时,我们可以再次选择1艘货船1,更新 dp[6]
:
dp[6] = Math.max(dp[6], dp[3] + 1 * 2) = Math.max(0, 2 + 2) = 4
这会导致货船1被选择两次,违反了题目要求。
反向遍历的正确性
通过反向遍历,我们可以避免这种情况。具体步骤如下:
- 外层循环:遍历每一种类型的货船。
- 内层循环:从高预算到低预算遍历
dp
数组。 - 嵌套循环:尝试租用不同数量的该类型货船。
for (List<Integer> ship : ships) {
int m = ship.get(0); // 该类型货船的数量
int v = ship.get(1); // 该类型货船的租赁成本
int w = ship.get(2); // 该类型货船的最大载货量
// 使用反向遍历来避免重复计算
for (int j = V; j >= v; j--) {
// 尝试租用不同数量的该类型货船
for (int k = 1; k <= m && k * v <= j; k++) {
dp[j] = Math.max(dp[j], dp[j - k * v] + k * w);
}
}
}
详细解释
- 外层循环:
-
- 遍历每一种类型的货船,获取其数量
m
、成本v
和最大载货量w
。
- 遍历每一种类型的货船,获取其数量
- 内层循环:
-
- 从高预算
V
到最低成本v
遍历dp
数组。 - 这样做的目的是确保在更新
dp[j]
时,dp[j - k * v]
已经是上一步的结果,不会被当前步的更新影响。
- 从高预算
- 嵌套循环:
-
- 尝试租用
1
到m
艘该类型货船。 - 确保租用
k
艘货船的成本不超过当前预算j
。 - 更新
dp[j]
的值为dp[j - k * v] + k * w
和当前dp[j]
值中的较大者。
- 尝试租用
示例说明
假设我们有以下数据:
- 货船1:数量
m = 2
,成本v = 3
,载货量w = 2
- 预算
V = 10
- 初始化
dp
数组:
int[] dp = new int[11]; // dp[0] 到 dp[10]
- 遍历货船1:
-
m = 2
,v = 3
,w = 2
- 反向遍历
dp
数组,从j = 10
到j = 3
:
-
-
- 当
j = 10
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[10] = Math.max(dp[10], dp[7] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用2艘货船1:
dp[10] = Math.max(dp[10], dp[4] + 4) = Math.max(2, 0 + 4) = 4
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 9
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[9] = Math.max(dp[9], dp[6] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用2艘货船1:
dp[9] = Math.max(dp[9], dp[3] + 4) = Math.max(2, 0 + 4) = 4
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 8
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[8] = Math.max(dp[8], dp[5] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用2艘货船1:
dp[8] = Math.max(dp[8], dp[2] + 4) = Math.max(2, 0 + 4) = 4
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 7
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[7] = Math.max(dp[7], dp[4] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 6
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[6] = Math.max(dp[6], dp[3] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 5
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[5] = Math.max(dp[5], dp[2] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 4
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[4] = Math.max(dp[4], dp[1] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用1艘货船1:
-
-
-
-
- 当
j = 3
时:
- 当
-
-
-
-
- 尝试租用1艘货船1:
dp[3] = Math.max(dp[3], dp[0] + 2) = Math.max(0, 0 + 2) = 2
- 尝试租用1艘货船1:
-
-
通过反向遍历,我们确保了在更新 dp[j]
时,dp[j - k * v]
是上一步的结果,不会被当前步的更新影响,从而避免了重复选择同一个物品的问题。
(字符串操作)易 103 完美整数
(转换为字符串去操作)
问题描述
一个整数如果由相同的数字构成,则称为完美整数。例如:
1
、11
、333
是完美整数。12
、19
、101
是不完美整数。
现在,你需要计算给定区间 [x, y]
中有多少个整数是完美整数。
测试样例
样例1:
输入:x = 1 ,y = 10
输出:9
样例2:
输入:x = 2 ,y = 22
输出:10
public class Main {
/**
* 判断一个整数是否是完美整数
* 完美整数是指由相同的数字构成的整数,例如:1, 11, 333
*
* @param num 需要判断的整数
* @return 如果是完美整数返回true,否则返回false
*/
public static boolean isPerfectNumber(int num) {
// 将整数转换为字符串
String numStr = Integer.toString(num);
// 获取字符串的第一个字符
char firstChar = numStr.charAt(0);
// 遍历字符串中的每个字符,检查是否与第一个字符相同
for (int i = 1; i < numStr.length(); i++) {
if (numStr.charAt(i) != firstChar) {
return false; // 如果有一个字符不同,则不是完美整数
}
}
return true; // 所有字符都相同,是完美整数
}
/**
* 计算给定区间 [x, y] 中有多少个整数是完美整数
*
* @param x 区间下限
* @param y 区间上限
* @return 完美整数的数量
*/
public static int solution(int x, int y) {
int count = 0; // 用于计数完美整数的数量
// 遍历区间 [x, y] 中的每个整数
for (int i = x; i <= y; i++) {
if (isPerfectNumber(i)) {
count++; // 如果是完美整数,计数加一
}
}
return count; // 返回完美整数的数量
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(1, 10) == 9); // 预期输出:true
System.out.println(solution(2, 22) == 10); // 预期输出:true
}
}
易 154 小C点菜问题
问题描述
小C来到了一家餐馆,准备点一些菜。
已知该餐馆有 nn 道菜,第 ii 道菜的售价为 wiwi。
小C准备点一些价格相同的菜,但小C不会点单价超过 mm 的菜。
小C想知道,自己最多可以点多少道菜?
测试样例
样例1:
输入:m = 6, w = [2, 3, 3, 6, 6, 6, 9, 9, 23]
输出:3
样例2:
输入:m = 4, w = [1, 2, 4, 4, 4]
输出:3
样例3:
输入:m = 5, w = [5, 5, 5, 5, 6, 7, 8]
输出:4
数组
public class Main {
public static long solution(int m, int[] w) {
// 创建一个数组来存储每个价格出现的次数
int[] priceCount = new int[m + 1];
// 遍历所有菜的价格,统计每个价格出现的次数
for (int price : w) {
if (price <= m) {
priceCount[price]++;
}
}
// 初始化最大数量为0
int maxCount = 0;
// 遍历价格数组,找出出现次数最多的价格
for (int count : priceCount) {
if (count > maxCount) {
maxCount = count;
}
}
// 返回最大数量
return maxCount;
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(6, new int[]{2, 3, 3, 6, 6, 6, 9, 9, 23}) == 3);
// 测试样例2
System.out.println(solution(4, new int[]{1, 2, 4, 4, 4}) == 3);
// 测试样例3
System.out.println(solution(5, new int[]{5, 5, 5, 5, 6, 7, 8}) == 4);
}
}
哈希表
import java.util.HashMap;
public class Main {
public static long solution(int m, int[] w) {
// 创建一个HashMap来存储每个价格出现的次数
HashMap<Integer, Integer> priceCount = new HashMap<>();
// 遍历所有菜的价格,统计每个价格出现的次数
for (int price : w) {
if (price <= m) {
// 如果价格已经存在于HashMap中,则增加计数;否则,初始化为1
priceCount.put(price, priceCount.getOrDefault(price, 0) + 1);
}
}
// 初始化最大数量为0
int maxCount = 0;
// 遍历HashMap,找出出现次数最多的价格
for (int count : priceCount.values()) {
if (count > maxCount) {
maxCount = count;
}
}
// 返回最大数量
return maxCount;
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(6, new int[]{2, 3, 3, 6, 6, 6, 9, 9, 23}) == 3);
// 测试样例2
System.out.println(solution(4, new int[]{1, 2, 4, 4, 4}) == 3);
// 测试样例3
System.out.println(solution(5, new int[]{5, 5, 5, 5, 6, 7, 8}) == 4);
}
}
(变形)小C准备点一些价格相同的菜,但小C不会点总价超过 m 的菜。
import java.util.HashMap;
public class Main {
public static long solution(int m, int[] w) {
// 创建一个HashMap来存储每个价格出现的次数
HashMap<Integer, Integer> priceCount = new HashMap<>();
// 遍历所有菜的价格,统计每个价格出现的次数
for (int price : w) {
priceCount.put(price, priceCount.getOrDefault(price, 0) + 1);
}
// 初始化最大数量为0
int maxCount = 0;
// 遍历HashMap,计算每个价格最多可以点多少道菜
for (int price : priceCount.keySet()) {
// 计算最多可以点多少道该价格的菜
int count = Math.min(priceCount.get(price), m / price);
if (count > maxCount) {
maxCount = count;
}
}
// 返回最大数量
return maxCount;
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(6, new int[]{2, 3, 3, 6, 6, 6, 9, 9, 23}) == 3);
// 测试样例2
System.out.println(solution(4, new int[]{1, 2, 4, 4, 4}) == 2);
// 测试样例3
System.out.println(solution(5, new int[]{5, 5, 5, 5, 6, 7, 8}) == 1);
}
}
???中 162 小C的敏好数
问题描述
小C对“好数”非常感兴趣,她定义一个不含前导零的正整数为“好数”,如果它的所有数位最多包含两种不同的数字。例如,数字 23
,2323
,9
,111
,和 101
都是好数。现在小C想知道,从1到nn之间有多少个好数。
例如:n=110时,所有的1位数、2位数,以及一些3位数(如 100
, 101
)都是好数,一共有102个。
测试样例
样例1:
输入:n = 110
输出:102
样例2:
输入:n = 1000
输出:352
样例3:
输入:n = 1
输出:1
易 169 小C的排列询问
问题描述
小C拿到了一个排列,她想知道在这个排列中,元素 xx 和 yy 是否是相邻的。排列是一个长度为 nn 的数组,其中每个数字从 11 到 nn 恰好出现一次。
你的任务是判断在给定的排列中,xx 和 yy 是否是相邻的。
测试样例
样例1:
输入:n = 4, a = [1, 4, 2, 3], x = 2, y = 4
输出:True
样例2:
输入:n = 5, a = [3, 4, 5, 1, 2], x = 3, y = 2
输出:False
样例3:
输入:n = 6, a = [6, 1, 5, 2, 4, 3], x = 5, y = 2
输出:True
public class Main {
public static boolean solution(int n, int[] a, int x, int y) {
// 遍历数组,检查每一对相邻的元素
for (int i = 0; i < n - 1; i++) {
// 检查当前元素和下一个元素是否分别是 x 和 y,或者 y 和 x
if ((a[i] == x && a[i + 1] == y) || (a[i] == y && a[i + 1] == x)) {
// 如果找到相邻的 x 和 y,返回 true
return true;
}
}
// 如果遍历完整个数组都没有找到相邻的 x 和 y,返回 false
return false;
}
public static void main(String[] args) {
System.out.println(solution(4, new int[]{1, 4, 2, 3}, 2, 4) == true);
System.out.println(solution(5, new int[]{3, 4, 5, 1, 2}, 3, 2) == false);
System.out.println(solution(6, new int[]{6, 1, 5, 2, 4, 3}, 5, 2) == true);
}
}
中 202 小M的订单编号问题
(数学 取模运算)
问题描述
商家使用了循环订单编号系统,每次发起订单时,编号会递增,但当编号超过设置的上限 m 时,编号会从 1 重新开始编号。小M想知道,当编号上限为 m 时,第 x 个订单的编号是多少。
你需要处理多个查询,每个查询给定 m 和 x,输出第 x 个订单的编号。
测试样例
样例1:
输入:m = 2, x = 3
输出:1
样例2:
输入:m = 5, x = 17
输出:2
样例3:
输入:m = 8, x = 2
输出:2
样例4:
输入:m = 4, x = 4
输出:4
public class Main {
/**
* 计算第 x 个订单的编号,编号上限为 m
*
* @param m 编号上限
* @param x 订单编号
* @return 第 x 个订单的编号
*/
public static int solution(int m, int x) {
// 使用取模运算来计算第 x 个订单的编号
// 当编号超过上限 m 时,编号会从 1 重新开始编号
// 因此,第 x 个订单的编号可以通过 (x - 1) % m + 1 来计算
return (x - 1) % m + 1;
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(2, 3) == 1); // 输出 true
System.out.println(solution(5, 17) == 2); // 输出 true
System.out.println(solution(8, 2) == 2); // 输出 true
System.out.println(solution(4, 4) == 4); // 输出 true
}
}
难 209 小R的子数组权值
(哈希表存储 遍历所有可能)
问题描述
小R有一个长度为 n
的数组 a
,她定义每个子区间 [l, r]
的权值为 a[l] | a[l+1] | ... | a[r]
,即该区间内所有元素的按位或运算结果。小R非常好奇,在这 n × (n + 1) / 2
个子区间中,究竟有多少种不同的权值。
她希望你能帮她计算一下,所有子区间中的不同权值总共有多少种。
测试样例
样例1:
输入:a = [1, 2, 4]
输出:6
样例2:
输入:a = [5, 3, 8, 1]
输出:8
样例3:
输入:a = [1, 1]
输出:1
样例4:
输入:a = [7, 8, 9, 10, 11]
输出:6
import java.util.HashSet;
import java.util.Set;
public class Main {
/**
* 计算给定数组所有非空连续子数组的按位或结果中不重复值的数量。
*
* @param a 输入的整数数组
* @return 不重复的按位或结果的数量
*/
public static int solution(int[] a) {
// 使用 HashSet 来存储不同的按位或结果
Set<Integer> uniqueValues = new HashSet<>();
// 遍历所有可能的子区间
for (int l = 0; l < a.length; l++) { // l 是子区间的起始位置
int currentOr = 0; // 初始化当前子区间的按位或结果为 0
for (int r = l; r < a.length; r++) { // r 是子区间的结束位置
// 计算当前子区间的按位或结果
currentOr |= a[r]; // 按位或操作,将当前元素 a[r] 与 currentOr 进行按位或运算
// 将结果添加到 HashSet 中
uniqueValues.add(currentOr); // 如果结果已经存在,则不会被再次添加
}
}
// 返回 HashSet 的大小,即不同按位或结果的数量
return uniqueValues.size();
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(new int[]{1, 2, 4}) == 6); // 输出: true
System.out.println(solution(new int[]{5, 3, 8, 1}) == 8); // 输出: true
System.out.println(solution(new int[]{1, 1}) == 1); // 输出: true
System.out.println(solution(new int[]{7, 8, 9, 10, 11}) == 6); // 输出: true
}
}
中 234 小U的奖学金申请问题
问题描述
小U是一位努力的学生,她正在申请奖学金。她修了nn门课程,每门课程有一个对应的学分aiai,并取得了一个成绩bibi。学校的奖学金评定要求是:小U所有课程的加权平均分不低于标准XX,并且所有课程的成绩都及格(即每门课的成绩不得低于6060分)。小U想知道她是否满足奖学金的申请条件。
测试样例
样例1:
输入:n = 4, x = 75, a = [4, 3, 2, 1], b = [80, 70, 90, 60]
输出:True
样例2:
输入:n = 3, x = 85, a = [3, 2, 5], b = [90, 85, 88]
输出:True
样例3:
输入:n = 5, x = 80, a = [2, 2, 3, 1, 4], b = [78, 80, 79, 85, 88]
输出:True
样例4:
输入:n = 6, x = 70, a = [4, 3, 2, 1, 2, 3], b = [60, 72, 65, 90, 85, 95]
输出:True
样例5:
输入:n = 4, x = 90, a = [2, 2, 2, 2], b = [89, 91, 92, 85]
输出:False
public class Main {
/**
* 判断小U是否满足奖学金的申请条件
* @param n 课程数量
* @param x 加权平均分标准
* @param a 每门课程的学分数组
* @param b 每门课程的成绩数组
* @return 如果满足条件返回true,否则返回false
*/
public static boolean solution(int n, int x, int[] a, int[] b) {
// 计算总学分
int totalCredits = 0;
for (int credit : a) {
totalCredits += credit;
}
// 计算加权总分
int weightedSum = 0;
for (int i = 0; i < n; i++) {
// 检查是否有不及格的课程
if (b[i] < 60) {
return false;
}
weightedSum += a[i] * b[i];
}
// 计算加权平均分
double weightedAverage = (double) weightedSum / totalCredits;
// 判断加权平均分是否达到标准
return weightedAverage >= x;
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution(4, 75, new int[]{4, 3, 2, 1}, new int[]{80, 70, 90, 60}) == true);
System.out.println(solution(3, 85, new int[]{3, 2, 5}, new int[]{90, 85, 88}) == true);
System.out.println(solution(5, 80, new int[]{2, 2, 3, 1, 4}, new int[]{78, 80, 79, 85, 88}) == true);
System.out.println(solution(6, 70, new int[]{4, 3, 2, 1, 2, 3}, new int[]{60, 72, 65, 90, 85, 95}) == true);
System.out.println(solution(4, 90, new int[]{2, 2, 2, 2}, new int[]{89, 91, 92, 85}) == false);
}
}
中 240 小U的矩转置计算
问题描述
小U拿到一个大小为 n × n
的矩阵。她需要计算该矩阵的转置权值。转置权值的定义如下:
- 将矩阵进行转置操作,转置后的矩阵中的元素
b[i][j]
与原矩阵中的元素a[j][i]
互换位置。 - 对于每个位置
(i, j)
,计算原矩阵的元素与转置矩阵对应位置元素的差的绝对值。 - 最后,将所有差的绝对值累加起来,得出转置权值。
测试样例
样例1:
输入:n = 2, a = [[1, 2], [3, 4]]
输出:2
样例2:
输入:n = 3, a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
输出:16
样例3:
输入:n = 4, a = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]]
输出:20
public class Main {
/**
* 计算矩阵的转置权值
* @param n 矩阵的大小 n x n
* @param a 输入的矩阵
* @return 转置权值
*/
public static int solution(int n, int[][] a) {
int transposeWeight = 0; // 初始化转置权值
// 遍历矩阵中的每一个元素
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 计算原矩阵元素与转置矩阵对应位置元素的差的绝对值
// 转置矩阵中 b[i][j] = a[j][i]
int diff = Math.abs(a[i][j] - a[j][i]);
// 累加到转置权值中
transposeWeight += diff;
}
}
return transposeWeight; // 返回最终的转置权值
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(2, new int[][]{{1, 2}, {3, 4}}) == 2); // 预期输出:true
// 测试样例2
System.out.println(solution(3, new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}) == 16); // 预期输出:true
// 测试样例3
System.out.println(solution(4, new int[][]{{1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3}, {4, 4, 4, 4}}) == 20); // 预期输出:true
}
}
易 267 数组重排最小化差值
问题描述
小C 和小U 有两个数组,分别是 a
和 b
,它们的长度相同。小U 想通过重新排列数组 a
的元素,来最小化 a
和 b
之间的差异。具体来说,他们要最小化所有元素差值绝对值之和,即 sum(abs(a[i] - b[i]))
。
你能帮助小C 和小U 找到这个最小化的值吗?
测试样例
样例1:
输入:a = [2, 1, 3, 2], b = [5, 2, 4, 2]
输出:5
样例2:
输入:a = [1, 4, 6], b = [2, 5, 7]
输出:3
样例3:
输入:a = [1, 9, 6], b = [2, 5, 7]
输出:4
当然可以!下面是对上述 Java 代码的详细注释和思路解析:
import java.util.Arrays;
public class Main {
/**
* 计算两个数组 a 和 b 之间元素差值绝对值之和的最小值。
* 通过重新排列数组 a 的元素,目标是最小化 sum(abs(a[i] - b[i]))。
*
* @param a 第一个数组
* @param b 第二个数组
* @return 最小化的差值绝对值之和
*/
public static long solution(int[] a, int[] b) {
// 对数组 a 和 b 进行排序
// 排序的目的是为了让两个数组中的元素尽可能匹配,从而最小化每对元素之间的差值
Arrays.sort(a);
Arrays.sort(b);
// 初始化总差值变量
long totalDifference = 0;
// 遍历排序后的数组,计算每对元素差值的绝对值,并累加到 totalDifference
for (int i = 0; i < a.length; i++) {
// 计算当前索引位置上的元素差值的绝对值
totalDifference += Math.abs(a[i] - b[i]);
}
// 返回计算得到的总差值
return totalDifference;
}
public static void main(String[] args) {
// 测试样例1
System.out.println(solution(new int[]{2, 1, 3, 2}, new int[]{5, 2, 4, 2}) == 5); // 应输出 true
// 测试样例2
System.out.println(solution(new int[]{1, 4, 6}, new int[]{2, 5, 7}) == 3); // 应输出 true
// 测试样例3
System.out.println(solution(new int[]{1, 9, 6}, new int[]{2, 5, 7}) == 4); // 应输出 true
}
}
详细思路解析
- 问题理解:
-
- 我们有两个数组
a
和b
,长度相同。 - 目标是通过重新排列数组
a
的元素,使得sum(abs(a[i] - b[i]))
达到最小值。
- 我们有两个数组
- 关键思想:
-
- 排序:通过对两个数组进行排序,可以使数组
a
和b
中的元素尽可能匹配,从而最小化每对元素之间的差值。 - 贪心策略:排序后,数组
a
和b
中的元素按从小到大的顺序排列,这样可以确保每对元素之间的差值尽可能小。
- 排序:通过对两个数组进行排序,可以使数组
- 算法步骤:
-
- 排序:使用
Arrays.sort()
方法对数组a
和b
进行排序。 - 初始化:定义一个变量
totalDifference
来存储总的差值绝对值之和。 - 计算差值:遍历排序后的数组,计算每对元素差值的绝对值,并将其累加到
totalDifference
中。 - 返回结果:遍历完成后,返回
totalDifference
作为最终结果。
- 排序:使用
- 时间复杂度:
-
- 排序操作的时间复杂度是 (O(n \log n)),其中 (n) 是数组的长度。
- 遍历操作的时间复杂度是 (O(n))。
- 因此,总的时间复杂度是 (O(n \log n))。
- 空间复杂度:
-
- 排序操作通常需要 (O(\log n)) 的额外空间(取决于具体的排序算法实现)。
- 其他变量占用的空间是常数级别的 (O(1))。
- 因此,总的空间复杂度是 (O(\log n))。
中 273 最大异或和计算
问题描述
给定两个长度为 n
的数组 a
和 b
,定义 f(c)
为数组 c
的所有元素的总和。现在,你需要恰好删除数组 a
或者数组 b
中的一个元素,使得 f(a)
和 f(b)
的异或结果最大。请输出这个最大的异或和。
测试样例
样例1:
输入:n = 3,a = [1, 2, 3],b = [3, 2, 1]
输出:5
样例2:
输入:n = 4,a = [4, 5, 6, 7],b = [7, 8, 9, 10]
输出:51
样例3:
输入:n = 5,a = [10, 20, 30, 40, 50],b = [50, 40, 30, 20, 10]
输出:248
public class Main {
public static int solution(int n, int[] a, int[] b) {
// 计算数组 a 和 b 的初始总和
int sumA = 0;
int sumB = 0;
for (int num : a) sumA += num;
for (int num : b) sumB += num;
int maxXor = 0;
// 遍历数组 a,尝试删除每个元素
for (int i = 0; i < n; i++) {
int newSumA = sumA - a[i]; // 删除 a[i] 后的新总和
int xor = newSumA ^ sumB; // 计算异或结果
maxXor = Math.max(maxXor, xor); // 更新最大异或值
}
// 遍历数组 b,尝试删除每个元素
for (int i = 0; i < n; i++) {
int newSumB = sumB - b[i]; // 删除 b[i] 后的新总和
int xor = sumA ^ newSumB; // 计算异或结果
maxXor = Math.max(maxXor, xor); // 更新最大异或值
}
return maxXor; // 返回最大异或值
}
public static void main(String[] args) {
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{3, 2, 1}) == 5);
System.out.println(solution(4, new int[]{4, 5, 6, 7}, new int[]{7, 8, 9, 10}) == 51);
System.out.println(solution(5, new int[]{10, 20, 30, 40, 50}, new int[]{50, 40, 30, 20, 10}) == 248);
}
}
难 287 火车驶入驶出顺序验证问题
(数据结构 栈)
问题描述
小F在观察火车驶入和驶出休息区的顺序时,注意到休息区的结构类似于栈,即遵循先进后出的规则。她记录了火车驶入和驶出的顺序,并希望验证这些顺序是否可能实际发生。火车在进入休息区后可以按顺序驶入或停留,然后根据休息区的规则依次驶出。你的任务是帮助小F验证所记录的火车驶入和驶出顺序是否能够被满足。
例如:如果火车的驶入顺序是 1 2 3
,驶出顺序是 3 2 1
,这是可能的;如果驶出顺序是 3 1 2
,则是不可能的。
测试样例
样例1:
输入:n = 3, a = [1, 2, 3], b = [1, 2, 3]
输出:True
样例2:
输入:n = 3, a = [1, 2, 3], b = [3, 2, 1]
输出:True
样例3:
输入:n = 3, a = [1, 2, 3], b = [3, 1, 2]
输出:False
import java.util.Stack;
public class Main {
/**
* 验证火车驶入驶出顺序是否可能。
*
* @param n 火车数量
* @param a 火车驶入顺序数组
* @param b 火车驶出顺序数组
* @return 如果给定的驶出顺序是可能的,则返回true;否则返回false。
*/
public static boolean solution(int n, int[] a, int[] b) {
Stack<Integer> stack = new Stack<>(); // 创建一个栈来模拟火车的进出过程
int indexB = 0; // b数组的索引,表示当前需要验证的火车编号
// 遍历驶入顺序数组
for (int i = 0; i < n; i++) {
stack.push(a[i]); // 将当前火车压入栈中
// 当栈不为空且栈顶元素等于b数组中当前索引指向的值时,持续弹出
while (!stack.isEmpty() && stack.peek() == b[indexB]) {
stack.pop(); // 弹出栈顶元素
indexB++; // 移动到下一个需要验证的火车编号
}
}
// 如果栈为空,说明所有火车都能按照b的顺序驶出
return stack.isEmpty();
}
public static void main(String[] args) {
// 测试样例
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{1, 2, 3})); // 输出: true
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{3, 2, 1})); // 输出: true
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{3, 1, 2})); // 输出: false
}
}
详细解析
1. 方法签名
public static boolean solution(int n, int[] a, int[] b)
n
: 火车的数量。a
: 火车驶入休息区的顺序数组。b
: 火车驶出休息区的顺序数组。- 返回值:如果给定的驶出顺序是可能的,则返回
true
;否则返回false
。
2. 栈的初始化
Stack<Integer> stack = new Stack<>();
- 使用
Stack<Integer>
来模拟火车进入休息区(压入栈)和离开休息区(从栈中弹出)的过程。
3. 驶出顺序数组的索引
int indexB = 0;
indexB
用于跟踪b
数组中当前需要验证的火车编号。
4. 遍历驶入顺序数组
for (int i = 0; i < n; i++) {
stack.push(a[i]); // 将当前火车压入栈中
// 当栈不为空且栈顶元素等于b数组中当前索引指向的值时,持续弹出
while (!stack.isEmpty() && stack.peek() == b[indexB]) {
stack.pop(); // 弹出栈顶元素
indexB++; // 移动到下一个需要验证的火车编号
}
}
- 遍历驶入顺序数组
a
,将每辆火车编号压入栈中。 - 每次压入后,检查栈顶的火车编号是否与
b
数组中当前需要驶出的火车编号相同。 - 如果相同,则从栈中弹出该火车编号,并将
indexB
增加 1,表示已经验证了一辆火车。
5. 检查栈的状态
return stack.isEmpty();
- 最后,检查栈是否为空。如果栈为空,说明所有火车都能按照
b
数组的顺序驶出;否则,说明存在无法按照b
数组顺序驶出的情况。
6. 主函数
public static void main(String[] args) {
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{1, 2, 3})); // 输出: true
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{3, 2, 1})); // 输出: true
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{3, 1, 2})); // 输出: false
}
main
方法中包含几个测试用例,用于验证solution
方法的正确性。- 每个
System.out.println
语句都会调用solution
方法并打印结果。
中 293 粮食分配的可能性计算
问题描述
粮食公司需要将nn吨粮食分配给若干分销商,每个分销商能够获得的粮食数量等于将nn除以分销商数量,向下取整。问题是,计算在可能的分销商数量中,分销商获得的不同粮食数量有多少种可能。
例如,如果粮食总量为5吨,当分销商数量为1时,分销商获得5吨;当分销商数量为2时,分销商获得2吨;当分销商数量为3、4、5时,分销商获得的粮食依次为1吨。这样,不同的分配方案有3种可能。
测试样例
样例1:
输入:n = 5
输出:3
样例2:
输入:n = 7
输出:4
样例3:
输入:n = 10
输出:5
import java.util.HashSet;
import java.util.Set;
public class Main {
/**
* 计算给定数量的粮食可以被分配成多少种不同的数量给分销商。
*
* @param n 给定的粮食总量
* @return 不同分配数量的种类数
*/
public static int solution(int n) {
// 使用HashSet来存储不同的分配粮食数量,因为HashSet不允许重复元素
Set<Integer> uniqueAmounts = new HashSet<>();
// 遍历所有可能的分销商数量,从1到n(包括1和n)
for (int distributors = 1; distributors <= n; distributors++) {
// 计算每个分销商平均可以获得的粮食数量,使用整数除法,结果向下取整
int amountPerDistributor = n / distributors;
// 将计算出的分配数量添加到HashSet中,如果已经存在,则不会再次添加
uniqueAmounts.add(amountPerDistributor);
}
// 返回HashSet的大小,即不同分配数量的种类数
return uniqueAmounts.size();
}
/**
* 主函数,用于运行测试用例,验证solution方法的正确性。
*/
public static void main(String[] args) {
// 测试用例1: 当总粮食为5单位时,可以分配成3种不同的数量
System.out.println("Test Case 1: " + (solution(5) == 3)); // 应输出 true
// 测试用例2: 当总粮食为7单位时,可以分配成4种不同的数量
System.out.println("Test Case 2: " + (solution(7) == 4)); // 应输出 true
// 测试用例3: 当总粮食为10单位时,可以分配成5种不同的数量
System.out.println("Test Case 3: " + (solution(10) == 5)); // 应输出 true
}
}
中 313 0 1 背包最大价值问题
问题描述
一个旅行者外出旅行时需要将 n
件物品装入背包,背包的总容量为 m
。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。
给定两个整数数组 weights
和 values
,其中 weights[i]
表示第 i
个物品的重量,values[i]
表示第 i
个物品的价值。你需要输出在满足背包总容量为 m
的情况下,背包中物品的最大总价值。
测试样例
样例1:
输入:n = 3 ,weights = [2, 1, 3] ,values = [4, 2, 3] ,m = 3
输出:6
样例2:
输入:n = 4 ,weights = [1, 2, 3, 2] ,values = [10, 20, 30, 40] ,m = 5
输出:70
样例3:
输入:n = 2 ,weights = [1, 4] ,values = [5, 10] ,m = 4
输出:10
public class Main {
/**
* 解决0-1背包问题的函数
*
* @param n 物品的数量
* @param weights 每个物品的重量数组
* @param values 每个物品的价值数组
* @param m 背包的最大承重
* @return 在不超过最大承重的情况下,所能获得的最大价值
*/
public static int solution(int n, int[] weights, int[] values, int m) {
// 创建一个二维数组 dp,大小为 (n+1) x (m+1)
// dp[i][j] 表示前 i 个物品在不超过承重 j 的情况下能达到的最大价值
int[][] dp = new int[n + 1][m + 1];
// 初始化 dp 数组
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 边界条件:当没有物品或背包容量为0时,最大价值为0
if (i == 0 || j == 0) {
dp[i][j] = 0;
} else if (weights[i - 1] <= j) {
// 关键步骤:状态转移方程
// 如果当前物品的重量小于等于背包的剩余承重
// 则可以选择放入当前物品或不放入当前物品,取两者中的较大值
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
} else {
// 如果当前物品的重量大于背包的剩余承重
// 则不能放入当前物品,最大价值为不放入当前物品时的最大价值
dp[i][j] = dp[i - 1][j];
}
}
}
// 返回最终结果,即前 n 个物品在不超过承重 m 的情况下能达到的最大价值
return dp[n][m];
}
public static void main(String[] args) {
// 测试用例1
System.out.println(solution(3, new int[]{2, 1, 3}, new int[]{4, 2, 3}, 3) == 6); // 输出 true
// 测试用例2
System.out.println(solution(4, new int[]{1, 2, 3, 2}, new int[]{10, 20, 30, 40}, 5) == 70); // 输出 true
// 测试用例3
System.out.println(solution(2, new int[]{1, 4}, new int[]{5, 10}, 4) == 10); // 输出 true
}
}
易 330 二进制反码转换问题
问题描述
小C在学习二进制运算,他了解到每个非负整数都有其二进制表示。例如,整数 5
可以被表示为二进制 "101"
,整数 11
可以被表示为二进制 "1011"
,并且除了 N = 0
外,任何二进制表示中都不含前导零。
二进制的反码表示是将每个 1
变为 0
,每个 0
变为 1
。例如,二进制数 "101"
的二进制反码为 "010"
。现在小C想知道,给定一个十进制数 N
,它的二进制反码对应的十进制数是多少。
测试样例
样例1:
输入:N = 5
输出:2
样例2:
输入:N = 10
输出:5
样例3:
输入:N = 0
输出:1
public class Main {
public static int solution(int N) {
String binaryString = Integer.toBinaryString(N);
StringBuilder invertedBinary = new StringBuilder();
for (char bit : binaryString.toCharArray()) {
invertedBinary.append(bit == '0' ? '1' : '0');
}
int result = Integer.parseInt(invertedBinary.toString(), 2);
return result;
}
public static void main(String[] args) {
System.out.println(solution(5) == 2 ? 1 : 0);
System.out.println(solution(10) == 5 ? 1 : 0);
System.out.println(solution(0) == 1 ? 1 : 0);
}
}
中 331 二进制子字符串覆盖问题
问题描述
小M有一个二进制字符串 s
,以及一个正整数 n
。小M想知道,对于 [1, n]
范围内的每个整数,其二进制表示是否都是字符串 s
的某个子字符串。一个子字符串是字符串中的连续字符序列。
如果 s
包含 [1, n]
范围内每个整数的二进制表示,返回 true
,否则返回 false
。
测试样例
样例1:
输入:s = "0110",n = 3
输出:True
样例2:
输入:s = "1001",n = 4
输出:False
样例3:
输入:s = "1100101",n = 6
输出:True
import java.util.HashSet;
public class Main {
public static boolean solution(String s, int n) {
// 创建一个HashSet来存储字符串s的所有子字符串
HashSet<String> substrings = new HashSet<>();
// 计算n的二进制表示的最大长度
int maxLength = Integer.toBinaryString(n).length();
// 生成所有可能的子字符串并存储在HashSet中
for (int i = 0; i < s.length(); i++) {
for (int j = i + 1; j <= s.length() && j <= i + maxLength; j++) {
substrings.add(s.substring(i, j));
}
}
// 检查[1, n]范围内的每个整数的二进制表示是否存在于HashSet中
for (int i = 1; i <= n; i++) {
String binaryString = Integer.toBinaryString(i);
if (!substrings.contains(binaryString)) {
return false;
}
}
return true;
}
public static void main(String[] args) {
System.out.println(solution("0110", 3) == true ? 1 : 0);
System.out.println(solution("1001", 4) == false ? 1 : 0);
System.out.println(solution("1100101", 6) == true ? 1 : 0);
}
}
中 337 二阶行列式构造问题
问题描述
小S正在学习线性代数,她想要构造一个二阶行列式,其值恰好等于给定的整数 xx。行列式的每个元素都是不超过 20 的正整数。二阶行列式的值由公式 ∣abcd∣=ad−bcacbd=ad−bc 计算。请你帮助小S求出满足要求的二阶行列式个数。
测试样例
样例1:
输入:x = 2
输出:682
样例2:
输入:x = -3
输出:567
样例3:
输入:x = 0
输出:1360
public class Main {
/**
* 计算所有可能的 2x2 整数矩阵的数量,这些矩阵的行列式等于给定的整数 x。
* 矩阵的形式如下:
* | a b |
* | c d |
* 其中 a, b, c, d 的取值范围均为 1 至 20。
*
* @param x 行列式的值
* @return 满足条件的矩阵数量
*/
public static int solution(int x) {
int count = 0; // 初始化计数器为 0
// 四层嵌套循环,遍历所有可能的 a, b, c, d 组合
for (int a = 1; a <= 20; a++) { // a 的取值范围为 1 至 20
for (int b = 1; b <= 20; b++) { // b 的取值范围为 1 至 20
for (int c = 1; c <= 20; c++) { // c 的取值范围为 1 至 20
for (int d = 1; d <= 20; d++) { // d 的取值范围为 1 至 20
// 计算行列式的值
int determinant = a * d - b * c;
// 如果行列式的值等于 x,则计数器加一
if (determinant == x) {
count++;
}
}
}
}
}
return count; // 返回满足条件的矩阵数量
}
/**
* 主函数,用于测试 solution 方法。
*/
public static void main(String[] args) {
// 测试 case 1: 行列式的值为 2
System.out.println("测试 case 1: " + (solution(2) == 682));
// 测试 case 2: 行列式的值为 -3
System.out.println("测试 case 2: " + (solution(-3) == 567));
// 测试 case 3: 行列式的值为 0
System.out.println("测试 case 3: " + (solution(0) == 1360));
}
}
(滑动窗口)中 344 农场采摘水果问题
问题描述
小U正在探访一座农场,农场的果树排成一列,用整数数组 fruits
表示,每个元素 fruits[i]
是第 i
棵树上的水果种类。
小U有两个篮子,每个篮子只能装一种类型的水果,而且每个篮子可以装无限量的水果。小U从任意一棵树开始,必须从每棵树上恰好采摘一个水果,并且只能装入符合篮子中水果类型的果实。如果某棵树上的水果类型不符合篮子中的水果类型,则必须停止采摘。
请你帮助小U计算他最多可以采摘多少个水果。
例如:当 fruits = [1,2,3,2,2]
时,小U最多可以采摘的树是从第2棵开始,采摘到最后的 4 棵树,结果为 [2,3,2,2]
。
测试样例
样例1:
输入:fruits = [1,2,1,2]
输出:4
样例2:
输入:fruits = [2,0,1,2,2]
输出:3
样例3:
输入:fruits = [1,2,3,2,2,4]
输出:4
import java.util.HashMap;
public class Main {
public static int solution(int[] fruits) {
// 初始化变量
int left = 0, right = 0;
int maxFruits = 0;
HashMap<Integer, Integer> fruitCount = new HashMap<>();
// 扩展窗口
while (right < fruits.length) {
// 将当前水果加入窗口
int currentFruit = fruits[right];
fruitCount.put(currentFruit, fruitCount.getOrDefault(currentFruit, 0) + 1);
// 如果窗口中的水果种类超过两种,缩小窗口
while (fruitCount.size() > 2) {
int leftFruit = fruits[left];
fruitCount.put(leftFruit, fruitCount.get(leftFruit) - 1);
if (fruitCount.get(leftFruit) == 0) {
fruitCount.remove(leftFruit);
}
left++;
}
// 更新最大值
maxFruits = Math.max(maxFruits, right - left + 1);
right++;
}
return maxFruits;
}
public static void main(String[] args) {
System.out.println(solution(new int[]{1, 2, 1, 2}) == 4);
System.out.println(solution(new int[]{2, 0, 1, 2, 2}) == 3);
System.out.println(solution(new int[]{1, 2, 3, 2, 2, 4}) == 4);
}
}
中 360 可被K整除的子数组问题
问题描述
小F正在研究数组的子数组问题。给定一个整数数组 nums
和一个整数 k
,你需要找到数组中所有元素之和可以被 k
整除的非空子数组的数目。注意,子数组是数组中的连续部分。
测试样例
样例1:
输入:nums = [4,5,0,-2,-3,1] ,k = 5
输出:7
样例2:
输入:nums = [1,2,3] ,k = 3
输出:3
样例3:
输入:nums = [-1,2,9] ,k = 2
输出:2
import java.util.HashMap;
public class Main {
/**
* 计算数组中满足条件的子数组数量。
*
* @param nums 输入的整数数组。
* @param k 给定的正整数,用于检查子数组的和是否可以被其整除。
* @return 满足条件的子数组数量。
*/
public static int solution(int[] nums, int k) {
// 创建一个哈希表来存储前缀和的余数及其出现的次数
HashMap<Integer, Integer> remainderCount = new HashMap<>();
// 初始化余数为0的情况,因为前缀和为0的情况也是一种有效的情况
remainderCount.put(0, 1);
int prefixSum = 0; // 当前的前缀和
int count = 0; // 满足条件的子数组数量
for (int num : nums) {
// 更新前缀和
prefixSum += num;
// 计算当前前缀和的余数,确保余数非负
int remainder = (prefixSum % k + k) % k;
// 如果哈希表中存在相同的余数,说明存在满足条件的子数组
if (remainderCount.containsKey(remainder)) {
// 更新满足条件的子数组数量
count += remainderCount.get(remainder);
}
// 更新哈希表中当前余数的计数
remainderCount.put(remainder, remainderCount.getOrDefault(remainder, 0) + 1);
}
// 返回满足条件的子数组数量
return count;
}
/**
* 主方法,用于测试 solution 方法。
*
* @param args 命令行参数(本例中未使用)。
*/
public static void main(String[] args) {
// 测试用例1: 数组 {4, 5, 0, -2, -3, 1} 和 k=5 应返回 7
System.out.println(solution(new int[]{4, 5, 0, -2, -3, 1}, 5) == 7 ? "Test 1 Passed" : "Test 1 Failed");
// 测试用例2: 数组 {1, 2, 3} 和 k=3 应返回 3
System.out.println(solution(new int[]{1, 2, 3}, 3) == 3 ? "Test 2 Passed" : "Test 2 Failed");
// 测试用例3: 数组 {-1, 2, 9} 和 k=2 应返回 2
System.out.println(solution(new int[]{-1, 2, 9}, 2) == 2 ? "Test 3 Passed" : "Test 3 Failed");
}
}