分值: 100分
题目描述
服务之间交换的接口成功率作为服务调用关键质量特性,某个时间段内的接口失败率使用一个数组表示。
数组中每个元素都是单位时间内失败率数值,数组中的数值为0~100的整数,
给定一个数值(minAverageLost)表示某个时间段内平均失败率容忍值,即平均失败率小于等于minAverageLost.找出数组中最长时间段,
如果未找到则直接返回NULL。
输入描述
有两行内容,
第一行为 minAverageLost,
第二行为数组,数组元素通过空格(" ")分隔,
minAverageLost及数组中元素取值范围为0~100的整数,数组元素的个数不会超过100个
输出描述
找出平均值小于等于minAverageLost的最长时间段,输出数组下标对,格式{beginIndex}-{endIndex} (下标从0开始),
如果同时存在多个最长时间段,则输出多个下标对且下标对之间使用空格(" ")拼接,多个下标对按下标从小到大排序。
示例1
输入:
1
0 1 2 3 4
输出:
0-2
说明:
A、输入解释:minAverageLost=1,数组[0, 1, 2, 3, 4]
B、前3个元素的平均值为1,因此数组第一个至第三个数组下标,即0-2
示例2输入:
2
0 0 100 2 2 99 0 2
输出:
0-1 3-4 6-7
说明:
A、输入解释:minAverageLost=2,数组[0, 0, 100, 2, 2, 99, 0, 2]
B、通过计算小于等于2的最长时间段为:数组下标为0-1即[0, 0],数组下标为3-4即[2, 2],数组下标为6-7即[0, 2],这三个部分都满足平均值小于等2的要求,因此输出0-1 3-4 6-7
代码思路
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 读取目标平均值
int eve = in.nextInt();
// 读取并丢弃换行符(因为nextInt后光标还在当前行)
String x = in.nextLine();
// 读取整行数字并分割
String[] st = in.nextLine().split(" ");
// 将字符串数组转换为整数数组
int[] nums = new int[st.length];
for (int i = 0; i < st.length; i++) {
nums[i] = Integer.parseInt(st[i]);
}
// 调用核心方法找出满足条件的子数组
minAverageList(nums, eve);
}
public static void minAverageList(int[] nums, int eve) {
// dp[i]存储从0到i的前缀和(元素之和)
int[] dp = new int[nums.length];
// f[i]存储以i结尾的满足条件的最大子数组长度
int[] f = new int[nums.length];
// 哈希表用于记录前缀和的索引,但此处代码未实际使用,后续需要完善
Map<Integer, Integer> map = new HashMap<>();
// 初始化第一个元素的前缀和
dp[0] = nums[0];
// 如果第一个元素本身就小于目标平均值,则满足条件的长度为1
if (dp[0] < eve) {
f[0] = 1;
}
// 记录全局最大子数组长度
int maxl = f[0];
// 遍历数组,计算每个位置的最长满足条件子数组
for (int i = 1; i < nums.length; i++) {
// 计算前缀和
dp[i] = dp[i - 1] + nums[i];
// 情况1:从0到i的整个前缀都满足条件
if (dp[i] <= (i + 1) * eve) {
f[i] = i + 1;
} else {
// 情况2:向前查找满足条件的子数组
// 遍历j从i-1到0,k表示子数组长度(从1开始)
for (int j = i - 1, k = 1; j >= 0; j--, k++) {
// 如果从j+1到i的子数组和满足条件
// 即 dp[i]-dp[j] <= k*eve
if (dp[i] - dp[j] <= k * eve) {
f[i] = k;
}
}
}
// 更新全局最大值
maxl = Math.max(maxl, f[i]);
}
// 构建结果字符串,输出所有满足最大长度的子数组区间
StringBuffer st = new StringBuffer();
for (int i = 0; i < nums.length; i++) {
// 如果以i结尾的子数组长度等于最大值
if (f[i] == maxl) {
// 计算起始和结束索引(索引从0开始)
// 起始 = i - 长度 + 1, 结束 = i
st.append(i - maxl + 1 + "-" + i + " ");
}
}
System.out.println(st);
}
}
解题思路:
- 输入最小平均失败率和失败率数据。
- 使用前缀和数组
psum记录从头开始到每个位置的失败率累加和。 - 使用两层循环遍历所有可能的时间段,通过前缀和数组判断平均失败率是否小于等于给定的阈值。
- 如果找到一个满足条件的时间段,比较其长度是否大于当前已知的最大长度,更新最大长度和结果数组。
- 最终输出所有满足条件的最长时间段。
java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @author code5bug
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取目标平均损失值
int minAverageLost = scanner.nextInt();
// 读取所有损失值并存储到列表中
List<Integer> losts = new ArrayList<>();
while (scanner.hasNextInt()) {
losts.add(scanner.nextInt());
}
int n = losts.size(); // 获取损失值数量
// 计算前缀和数组,psum[i]表示前i个元素的和(psum[0]=0)
int[] psum = new int[n + 1];
for (int i = 0; i < n; i++) {
psum[i + 1] = psum[i] + losts.get(i);
}
int maxLength = 1; // 记录满足条件的最大子数组长度,初始值为1
List<int[]> result = new ArrayList<>(); // 存储所有满足最大长度的子数组区间
// 双重循环遍历所有可能的子数组
for (int l = 0; l < n; l++) { // l表示子数组起始位置
// 优化:从l+maxLength-1开始遍历,跳过长度小于当前最大值的子数组
for (int r = l + maxLength - 1; r < n; r++) {
int length = r - l + 1; // 计算当前子数组长度
// 检查子数组l到r的平均损失是否小于等于目标值
// 等价于:子数组和 <= 目标平均值 × 长度
if (psum[r + 1] - psum[l] <= minAverageLost * length) {
// 找到更长的时间段
if (length > maxLength) {
maxLength = length; // 更新最大长度
result.clear(); // 清空之前的结果
result.add(new int[]{l, r}); // 添加新区间
} else if (length == maxLength) { // 长度与当前最大值相等
result.add(new int[]{l, r}); // 添加新的区间
}
}
}
}
// 打印输出结果
for (int[] p : result) {
System.out.print(p[0] + "-" + p[1] + " ");
}
}
}