【华为OD】查找接口成功率最优时间段
题目描述
服务之间交换的接口成功率作为服务调用关键质量特性,某个时间段内的接口失败率使用一个数组表示,数组中每个元素都是单位时间内失败率数值,数组中的数值为 0~100 的整数,给定一个数值(minAverageLost)表示某个时间段内平均失败率容忍值,即平均失败率小于等于 minAverageLost,找出数组中最长时间段,如果未找到则直接返回 NULL。
输入描述
输入有两行内容,第一行为 minAverageLost,第二行为数组,数组元素通过空格" "分隔,minAverageLost 及数组中元素取值范围为 0~100 的整数,数组元素的个数不会超过 100 个。
输出描述
找出平均值小于等于 minAverageLost 的最长时间段,输出数组下标对,格式{beginIndex}-{endIndex}(下标从 0 开始),如果同时存在多个最长时间段,则输出多个下标对且下标对之间使用空格" "拼接,多个下标对按下标从小到大排序。
示例
示例一
输入:
1
0 1 2 3 4
输出:
0-2
说明:
- A、输入解释:minAverageLost=1,数组 [0, 1, 2, 3, 4]
- B、前 3 个元素的平均值为 1,因此数组第一个至第三个数组下标,即 0-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实现
java
import java.util.*;
public class Solution1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int minAverageLost = sc.nextInt();
sc.nextLine(); // 消费换行符
String[] numStrs = sc.nextLine().split(" ");
int[] nums = new int[numStrs.length];
for (int i = 0; i < numStrs.length; i++) {
nums[i] = Integer.parseInt(numStrs[i]);
}
int n = nums.length;
int maxLen = 0;
List<String> results = new ArrayList<>();
// 枚举所有可能的子数组
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
int len = j - i + 1;
double avg = (double) sum / len;
// 检查是否满足条件
if (avg <= minAverageLost) {
if (len > maxLen) {
// 找到更长的子数组,清空之前的结果
maxLen = len;
results.clear();
results.add(i + "-" + j);
} else if (len == maxLen) {
// 找到相同长度的子数组,添加到结果中
results.add(i + "-" + j);
}
}
}
}
if (results.isEmpty()) {
System.out.println("NULL");
} else {
System.out.println(String.join(" ", results));
}
sc.close();
}
}
Python实现
python
def solve_brute_force():
min_average_lost = int(input())
nums = list(map(int, input().split()))
n = len(nums)
max_len = 0
results = []
# 枚举所有可能的子数组
for i in range(n):
current_sum = 0
for j in range(i, n):
current_sum += nums[j]
length = j - i + 1
avg = current_sum / length
# 检查是否满足条件
if avg <= min_average_lost:
if length > max_len:
# 找到更长的子数组,清空之前的结果
max_len = length
results = [f"{i}-{j}"]
elif length == max_len:
# 找到相同长度的子数组,添加到结果中
results.append(f"{i}-{j}")
if not results:
print("NULL")
else:
print(" ".join(results))
solve_brute_force()
C++实现
cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
int main() {
int minAverageLost;
cin >> minAverageLost;
cin.ignore(); // 忽略换行符
string line;
getline(cin, line);
istringstream iss(line);
vector<int> nums;
int num;
while (iss >> num) {
nums.push_back(num);
}
int n = nums.size();
int maxLen = 0;
vector<string> results;
// 枚举所有可能的子数组
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
int len = j - i + 1;
double avg = (double)sum / len;
// 检查是否满足条件
if (avg <= minAverageLost) {
if (len > maxLen) {
// 找到更长的子数组,清空之前的结果
maxLen = len;
results.clear();
results.push_back(to_string(i) + "-" + to_string(j));
} else if (len == maxLen) {
// 找到相同长度的子数组,添加到结果中
results.push_back(to_string(i) + "-" + to_string(j));
}
}
}
}
if (results.empty()) {
cout << "NULL" << endl;
} else {
for (int i = 0; i < results.size(); i++) {
if (i > 0) cout << " ";
cout << results[i];
}
cout << endl;
}
return 0;
}
解法二:滑动窗口优化法
使用滑动窗口的思想,对于每个起始位置,逐步扩展窗口直到不满足条件为止。
Java实现
java
import java.util.*;
public class Solution2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int minAverageLost = sc.nextInt();
sc.nextLine(); // 消费换行符
String[] numStrs = sc.nextLine().split(" ");
int[] nums = new int[numStrs.length];
for (int i = 0; i < numStrs.length; i++) {
nums[i] = Integer.parseInt(numStrs[i]);
}
int n = nums.length;
int maxLen = 0;
List<String> results = new ArrayList<>();
// 对每个起始位置使用滑动窗口
for (int start = 0; start < n; start++) {
int sum = 0;
for (int end = start; end < n; end++) {
sum += nums[end];
int len = end - start + 1;
double avg = (double) sum / len;
if (avg <= minAverageLost) {
// 满足条件,检查是否需要更新结果
if (len > maxLen) {
maxLen = len;
results.clear();
results.add(start + "-" + end);
} else if (len == maxLen) {
results.add(start + "-" + end);
}
} else {
// 不满足条件,但继续尝试更长的窗口
// 因为后面可能有更小的数字使平均值降低
continue;
}
}
}
if (results.isEmpty()) {
System.out.println("NULL");
} else {
System.out.println(String.join(" ", results));
}
sc.close();
}
}
Python实现
python
def solve_sliding_window():
min_average_lost = int(input())
nums = list(map(int, input().split()))
n = len(nums)
max_len = 0
results = []
# 对每个起始位置使用滑动窗口
for start in range(n):
current_sum = 0
for end in range(start, n):
current_sum += nums[end]
length = end - start + 1
avg = current_sum / length
if avg <= min_average_lost:
# 满足条件,检查是否需要更新结果
if length > max_len:
max_len = length
results = [f"{start}-{end}"]
elif length == max_len:
results.append(f"{start}-{end}")
if not results:
print("NULL")
else:
print(" ".join(results))
solve_sliding_window()
C++实现
cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
int main() {
int minAverageLost;
cin >> minAverageLost;
cin.ignore();
string line;
getline(cin, line);
istringstream iss(line);
vector<int> nums;
int num;
while (iss >> num) {
nums.push_back(num);
}
int n = nums.size();
int maxLen = 0;
vector<string> results;
// 对每个起始位置使用滑动窗口
for (int start = 0; start < n; start++) {
int sum = 0;
for (int end = start; end < n; end++) {
sum += nums[end];
int len = end - start + 1;
double avg = (double)sum / len;
if (avg <= minAverageLost) {
// 满足条件,检查是否需要更新结果
if (len > maxLen) {
maxLen = len;
results.clear();
results.push_back(to_string(start) + "-" + to_string(end));
} else if (len == maxLen) {
results.push_back(to_string(start) + "-" + to_string(end));
}
}
}
}
if (results.empty()) {
cout << "NULL" << endl;
} else {
for (int i = 0; i < results.size(); i++) {
if (i > 0) cout << " ";
cout << results[i];
}
cout << endl;
}
return 0;
}
解法三:优化的枚举法(推荐)
在暴力枚举的基础上进行一些优化,提高代码的可读性和效率。
Java实现
java
import java.util.*;
public class Solution3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int minAverageLost = sc.nextInt();
sc.nextLine();
String[] numStrs = sc.nextLine().split(" ");
int[] nums = new int[numStrs.length];
for (int i = 0; i < numStrs.length; i++) {
nums[i] = Integer.parseInt(numStrs[i]);
}
List<String> result = findLongestValidSegments(nums, minAverageLost);
if (result.isEmpty()) {
System.out.println("NULL");
} else {
System.out.println(String.join(" ", result));
}
sc.close();
}
private static List<String> findLongestValidSegments(int[] nums, int threshold) {
int n = nums.length;
int maxLen = 0;
List<String> results = new ArrayList<>();
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
int len = j - i + 1;
if (sum <= threshold * len) { // 避免浮点数计算
if (len > maxLen) {
maxLen = len;
results.clear();
results.add(i + "-" + j);
} else if (len == maxLen) {
results.add(i + "-" + j);
}
}
}
}
return results;
}
}
Python实现
python
def find_longest_valid_segments(nums, threshold):
n = len(nums)
max_len = 0
results = []
for i in range(n):
current_sum = 0
for j in range(i, n):
current_sum += nums[j]
length = j - i + 1
if current_sum <= threshold * length: # 避免浮点数计算
if length > max_len:
max_len = length
results = [f"{i}-{j}"]
elif length == max_len:
results.append(f"{i}-{j}")
return results
def solve_optimized():
min_average_lost = int(input())
nums = list(map(int, input().split()))
result = find_longest_valid_segments(nums, min_average_lost)
if not result:
print("NULL")
else:
print(" ".join(result))
solve_optimized()
C++实现
cpp
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
vector<string> findLongestValidSegments(const vector<int>& nums, int threshold) {
int n = nums.size();
int maxLen = 0;
vector<string> results;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
int len = j - i + 1;
if (sum <= threshold * len) { // 避免浮点数计算
if (len > maxLen) {
maxLen = len;
results.clear();
results.push_back(to_string(i) + "-" + to_string(j));
} else if (len == maxLen) {
results.push_back(to_string(i) + "-" + to_string(j));
}
}
}
}
return results;
}
int main() {
int minAverageLost;
cin >> minAverageLost;
cin.ignore();
string line;
getline(cin, line);
istringstream iss(line);
vector<int> nums;
int num;
while (iss >> num) {
nums.push_back(num);
}
vector<string> result = findLongestValidSegments(nums, minAverageLost);
if (result.empty()) {
cout << "NULL" << endl;
} else {
for (int i = 0; i < result.size(); i++) {
if (i > 0) cout << " ";
cout << result[i];
}
cout << endl;
}
return 0;
}
算法复杂度分析
解法一:暴力枚举法
- 时间复杂度:O(N²),需要枚举所有可能的子数组
- 空间复杂度:O(K),K为满足条件的最长子数组个数
解法二:滑动窗口优化法
- 时间复杂度:O(N²),虽然使用了滑动窗口的思想,但仍需要枚举所有子数组
- 空间复杂度:O(K),K为满足条件的最长子数组个数
解法三:优化的枚举法
- 时间复杂度:O(N²),但避免了浮点数计算,提高了实际运行效率
- 空间复杂度:O(K),K为满足条件的最长子数组个数
算法原理详解
核心思想
问题的本质是寻找满足平均值条件的最长连续子数组。对于子数组 [i, j]
,其平均值为:
avg = sum(nums[i...j]) / (j - i + 1)
要使 avg <= threshold
,等价于:
sum(nums[i...j]) <= threshold * (j - i + 1)
这样可以避免浮点数计算,提高精度和效率。
优化技巧
- 避免浮点数计算 :将
sum/len <= threshold
转换为sum <= threshold * len
- 及时更新结果:在遍历过程中及时更新最长长度和结果列表
- 结果去重:由于按顺序遍历,天然保证了结果的有序性
示例分析
示例一分析
数组:[0, 1, 2, 3, 4]
,threshold = 1
- 子数组
[0]
:平均值 = 0 ≤ 1 ✓,长度 = 1 - 子数组
[0, 1]
:平均值 = 0.5 ≤ 1 ✓,长度 = 2 - 子数组
[0, 1, 2]
:平均值 = 1 ≤ 1 ✓,长度 = 3 - 子数组
[0, 1, 2, 3]
:平均值 = 1.5 > 1 ✗
最长满足条件的子数组是 [0, 1, 2]
,输出 0-2
。
示例二分析
数组:[0, 0, 100, 2, 2, 99, 0, 2]
,threshold = 2
通过枚举所有子数组,找到满足条件的最长子数组长度为2,包括:
[0, 0]
(下标0-1):平均值 = 0 ≤ 2 ✓[2, 2]
(下标3-4):平均值 = 2 ≤ 2 ✓[0, 2]
(下标6-7):平均值 = 1 ≤ 2 ✓
输出 0-1 3-4 6-7
。
总结
三种解法本质上都是暴力枚举,但在实现细节上有所不同:
- 暴力枚举法:最直观的实现,使用浮点数计算平均值
- 滑动窗口优化法:思路类似,但代码结构更清晰
- 优化的枚举法 :避免浮点数计算,提高精度和效率,推荐使用
对于这道题目,由于数组长度不超过100,所有方法都能高效运行。关键在于正确理解题意,找出所有满足条件的最长子数组,并按要求格式输出。
核心技巧:
- 使用整数运算代替浮点数运算提高精度
- 在遍历过程中及时更新最优解
- 注意处理多个相同长度的最长子数组的情况
- 输出格式要严格按照题目要求