题目描述与示例

题目描述

服务之间交换的接口成功率作为服务调用关键质量特性,某个时间段内的接口失败率使用一个数组表示,数组中每个元素都是单位时间内失败率数值,数组中的数值为 0~100 的整数,给定一个数值(minAverageLost)表示某个时间段内平均失败率容忍值,即平均失败率小于等于 minAverageLost,找出数组中最长时间段,如果未找到则直接返回 NULL

题目练习网址:https://www.algomooc.com/problem/P3281

输入描述

输入有两行内容,第一行为minAverageLost,第二行为数组,数组元素通过空格" "分隔,minAverageLost 及数组中元素取值范围为 0~100 的整数,数组元素的个数不会超过 100 个。

输出描述

找出平均值小于等于 minAverageLost 的最长时间段,输出数组下标对,格式{beginIndex}-{endIndx}(下标从 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

解题思路

本题数据规模不大,可以用暴力法枚举所有的区间来解决。

暴力法就略去不表,这里主要讲解复杂度较优秀的解法。

贪心思想

由于题目要求我们找到数组中平均失败率小于等于 minAverageLost的中最长时间段,我们贪心地优先从区间长度更大的情况开始考虑

对于已知长度为n的数组nums而言,其中的连续子区间的长度l的最大值即为n

故我们可以从n开始到1结束,逆序遍历连续子区间的长度l,即

for l in range(n, 0, -1):
pass

一旦发现,对于某一个固定长度l,我们能够找到长度为l的连续区间的区间和的平均值小于等于minAverageLost,则说明我们一定找到的是最长的满足题意的区间。

将除法转换为乘法

由于计算平均值涉及到除法,我们在计算过程中应该尽量地避免除法(尤其是可能出现不整除的情况)。

对于某个特定的l,连续区间的长度已经确定为l,假设连续区间和为interval_sum,那么满足题意的式子interval_sum / l <= minAverageLost可以转化为interval_sum <= minAverageLost * l

设阈值threshold = minAverageLost * l,我们就可以把问题进一步转化为,求在特定l的情况下,存在哪一些区间满足条件interval_sum <= threshold了。

所以剩下的问题,就是解决如何方便地计算长度为l的连续区间和了。

计算连续子数组的和相关的题目,一般就是使用滑窗或者前缀和来解决

固定滑窗

对于某一个确定的l值,我们可以使用固定滑窗算法来计算连续区间和(即窗口和)interval_sum

整个过程的核心代码为

# 计算阈值threshold,
# 连续区间和必须小于这个阈值才可以
threshold = minAverageLost * l
# 初始化第一个窗口的窗口和
interval_sum = sum(nums[:l])
if interval_sum <= threshold:
ans.append(f"{0}-{l-1}")
# 固定滑窗过程
for right, num_right in enumerate(nums[l:], l):
# A1
interval_sum += num_right
# A2
interval_sum -= nums[right-l]
# A3
if interval_sum <= threshold:
# 储存的区间是左闭右闭区间,故左边界应该为right-l+1
ans.append(f"{right-l+1}-{right}")

前缀和

考虑前缀和技巧。构建前缀和数组为pre_sum(注意前缀和数组的大小比原数组nums多一位,为n+1)。

我们可以枚举所有区间的起始点i,那么所有长度为l的连续区间和可以表示为pre_sum[i+l]-pre_sum[i]

这里唯一的难点在于确定起始位置i的范围。我们可以通过取特殊边界值代入的方式来确定。若

  • 选取l = n

    • 原数组仅存在一个连续区间nums[0:n]
    • 区间和interval_sum = pre_sum[n]-pre_sum[0]
    • i的范围应该是range(0, 1)
    • 考虑右边界,1 = n - n + 1 = n - l + 1,故确定区间范围应该是range(0, n-l+1)
  • 选取l = 1
    • 考虑原数组最后一个连续区间nums[n-1:n]
    • 区间和interval_sum = pre_sum[n]-pre_sum[n-1]
    • i的范围应该是range(0, n)
    • 考虑右边界,n = n - 1 + 1 = n - l + 1,故确定区间范围应该是range(0, n-l+1)

故整个过程的核心代码为

# 计算阈值threshold,
# 连续区间和必须小于这个阈值才可以
threshold = minAverageLost * l
# 遍历区间的起始位置i,其范围为[0, n-l+1)
# 这里的范围,可以用特殊值代入法来确定:
# 选取特例l = n,那么n-l+1 = 1
# 由于前缀和数组的长度为n+1,因此选取pre_sum[i+l]才不越界
for i in range(0, n-l+1):
# 对于每一个区间的起始位置i,我们都需要考虑长度为l的区间[i:i+l]的区间和
# 使用前缀和计算区间和interval_sum
interval_sum = pre_sum[i+l] - pre_sum[i]
# 如果区间和小于等于阈值,则这个区间是满足题意的区间,将其加入ans中
if interval_sum <= threshold:
# 储存的区间是左闭右闭区间,故右边界应该为i+l-1
ans.append(f"{i}-{i+l-1}")

代码

解法一:前缀和

Python

# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com from itertools import accumulate # 输入
minAverageLost = int(input())
nums = list(map(int, input().split())) # 构建解决问题的函数
def solve(minAverageLost, nums):
# 数据长度
n = len(nums)
# 构建前缀和数组,注意首位需要填充一个0,表示不选取任何数字的前缀和
pre_sum = [0] + list(accumulate(nums))
# 构建答案数组
ans = list()
# 逆序遍历区间的长度l,
# 贪心地优先考虑尽可能大的区间
for l in range(n, 0, -1):
# 计算阈值threshold,
# 连续区间和必须小于这个阈值才可以
threshold = minAverageLost * l
# 遍历区间的起始位置i,其范围为[0, n-l+1)
# 这里的范围,可以用特殊值代入法来确定:
# 选取特例l = n,那么n-l+1 = 1
# 由于前缀和数组的长度为n+1,因此选取pre_sum[i+l]才不越界
for i in range(0, n-l+1):
# 对于每一个区间的起始位置i,我们都需要考虑长度为l的区间[i:i+l]的区间和
# 使用前缀和计算区间和interval_sum
interval_sum = pre_sum[i+l] - pre_sum[i]
# 如果区间和小于等于阈值,则这个区间是满足题意的区间,将其加入ans中
if interval_sum <= threshold:
# 储存的区间是左闭右闭区间,故右边界应该为i+l-1
ans.append(f"{i}-{i+l-1}")
# 在考虑大小为l的区间之后,如果ans中有值
# 则说明找到了最长的满足题意的区间,将ans合并后返回输出
if ans:
return " ".join(ans) # 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
# 此时应该返回"NULL"输出
return "NULL" # 调用函数并输出答案
print(solve(minAverageLost, nums))

Java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.StringJoiner; public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 输入
int minAverageLost = scanner.nextInt();
scanner.nextLine(); // 读取换行符
String line = scanner.nextLine();
String[] numStrs = line.split(" ");
int[] nums = new int[numStrs.length];
for (int i = 0; i < numStrs.length; i++) {
nums[i] = Integer.parseInt(numStrs[i]);
} // 调用函数并输出答案
System.out.println(solve(minAverageLost, nums));
} // 构建解决问题的函数
public static String solve(int minAverageLost, int[] nums) {
// 数据长度
int n = nums.length;
// 构建前缀和数组,注意首位需要填充一个0,表示不选取任何数字的前缀和
int[] preSum = new int[n + 1];
for (int i = 1; i <= n; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
} // 构建答案数组
List<String> ans = new ArrayList<>();
// 逆序遍历区间的长度l,
// 贪心地优先考虑尽可能大的区间
for (int l = n; l > 0; l--) {
// 计算阈值threshold,
// 连续区间和必须小于这个阈值才可以
int threshold = minAverageLost * l;
// 遍历区间的起始位置i,其范围为[0, n-l+1)
// 这里的范围,可以用特殊值代入法来确定:
// 选取特例l = n,那么n-l+1 = 1
// 由于前缀和数组的长度为n+1,因此选取preSum[i+l]才不越界
for (int i = 0; i <= n - l; i++) {
// 对于每一个区间的起始位置i,我们都需要考虑长度为l的区间[i:i+l]的区间和
// 使用前缀和计算区间和intervalSum
int intervalSum = preSum[i + l] - preSum[i];
// 如果区间和小于等于阈值,则这个区间是满足题意的区间,将其加入ans中
if (intervalSum <= threshold) {
// 储存的区间是左闭右闭区间,故右边界应该为i+l-1
ans.add(i + "-" + (i + l - 1));
}
}
// 在考虑大小为l的区间之后,如果ans中有值
// 则说明找到了最长的满足题意的区间,将ans合并后返回输出
if (!ans.isEmpty()) {
StringJoiner result = new StringJoiner(" ");
for (String s : ans) {
result.add(s);
}
return result.toString();
}
} // 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
// 此时应该返回"NULL"输出
return "NULL";
}
}

C++

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <numeric> using namespace std; string solve(int minAverageLost, const vector<int>& nums) {
// 数据长度
int n = nums.size();
// 构建前缀和数组,注意首位需要填充一个0,表示不选取任何数字的前缀和
vector<int> pre_sum(n + 1, 0);
partial_sum(nums.begin(), nums.end(), pre_sum.begin() + 1); // 构建答案数组
vector<string> ans;
// 逆序遍历区间的长度l,
// 贪心地优先考虑尽可能大的区间
for (int l = n; l > 0; --l) {
// 计算阈值threshold,
// 连续区间和必须小于这个阈值才可以
int threshold = minAverageLost * l;
// 遍历区间的起始位置i,其范围为[0, n-l+1)
// 这里的范围,可以用特殊值代入法来确定:
// 选取特例l = n,那么n-l+1 = 1
// 由于前缀和数组的长度为n+1,因此选取pre_sum[i+l]才不越界
for (int i = 0; i <= n - l; ++i) {
// 对于每一个区间的起始位置i,我们都需要考虑长度为l的区间[i:i+l]的区间和
// 使用前缀和计算区间和interval_sum
int interval_sum = pre_sum[i + l] - pre_sum[i];
// 如果区间和小于等于阈值,则这个区间是满足题意的区间,将其加入ans中
if (interval_sum <= threshold) {
// 储存的区间是左闭右闭区间,故右边界应该为i+l-1
ans.push_back(to_string(i) + "-" + to_string(i + l - 1));
}
}
// 在考虑大小为l的区间之后,如果ans中有值
// 则说明找到了最长的满足题意的区间,将ans合并后返回输出
if (!ans.empty()) {
string result;
for (size_t i = 0; i < ans.size(); ++i) {
if (i > 0) {
result += " ";
}
result += ans[i];
}
return result;
}
} // 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
// 此时应该返回"NULL"输出
return "NULL";
} int main() {
int minAverageLost;
// 输入
cin >> minAverageLost;
cin.ignore(); // 忽略换行符 string line;
getline(cin, line);
stringstream ss(line);
vector<int> nums;
int num; while (ss >> num) {
nums.push_back(num);
if (ss.peek() == ',') {
ss.ignore();
}
} // 调用函数并输出答案
cout << solve(minAverageLost, nums) << endl; return 0;
}

解法二:固定滑窗

Python

# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com # 输入
minAverageLost = int(input())
nums = list(map(int, input().split())) # 构建解决问题的函数
def solve(minAverageLost, nums):
# 数据长度
n = len(nums)
# 构建答案数组
ans = list()
# 逆序遍历区间的长度l,
# 贪心地优先考虑尽可能大的区间
for l in range(n, 0, -1):
# 计算阈值threshold,
# 连续区间和必须小于这个阈值才可以
threshold = minAverageLost * l
# 初始化第一个窗口的窗口和
interval_sum = sum(nums[:l])
if interval_sum <= threshold:
ans.append(f"{0}-{l-1}")
# 固定滑窗过程
for right, num_right in enumerate(nums[l:], l):
# A1
interval_sum += num_right
# A2
interval_sum -= nums[right-l]
# A3
if interval_sum <= threshold:
# 储存的区间是左闭右闭区间,故左边界应该为right-l+1
ans.append(f"{right-l+1}-{right}")
if len(ans) > 0:
return " ".join(ans) # 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
# 此时应该返回"NULL"输出
return "NULL" # 调用函数并输出答案
print(solve(minAverageLost, nums))

Java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner; public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 输入
int minAverageLost = scanner.nextInt();
scanner.nextLine(); // 读取换行符
String[] numStrs = scanner.nextLine().split(" ");
int[] nums = new int[numStrs.length];
for (int i = 0; i < numStrs.length; i++) {
nums[i] = Integer.parseInt(numStrs[i]);
} // 调用函数并输出答案
System.out.println(solve(minAverageLost, nums));
} // 构建解决问题的函数
public static String solve(int minAverageLost, int[] nums) {
// 数据长度
int n = nums.length;
// 构建答案数组
List<String> ans = new ArrayList<>();
// 逆序遍历区间的长度l,
// 贪心地优先考虑尽可能大的区间
for (int l = n; l > 0; l--) {
// 计算阈值threshold,
// 连续区间和必须小于这个阈值才可以
int threshold = minAverageLost * l;
// 初始化第一个窗口的窗口和
int interval_sum = 0;
for (int i = 0; i < l; i++) {
interval_sum += nums[i];
}
if (interval_sum <= threshold) {
ans.add("0-" + (l - 1));
}
// 固定滑窗过程
for (int right = l; right < n; right++) {
// A1
interval_sum += nums[right];
// A2
interval_sum -= nums[right - l];
// A3
if (interval_sum <= threshold) {
// 储存的区间是左闭右闭区间,故左边界应该为right-l+1
ans.add((right - l + 1) + "-" + right);
}
}
if (!ans.isEmpty()) {
return String.join(" ", ans);
}
} // 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
// 此时应该返回"NULL"输出
return "NULL";
}
}

C++

#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm> using namespace std; string solve(int minAverageLost, const vector<int>& nums) {
// 数据长度
int n = nums.size();
// 构建答案数组
vector<string> ans;
// 逆序遍历区间的长度l,
// 贪心地优先考虑尽可能大的区间
for (int l = n; l > 0; --l) {
// 计算阈值threshold,
// 连续区间和必须小于这个阈值才可以
int threshold = minAverageLost * l;
// 初始化第一个窗口的窗口和
int interval_sum = 0;
for (int i = 0; i < l; ++i) {
interval_sum += nums[i];
}
if (interval_sum <= threshold) {
ans.push_back(to_string(0) + "-" + to_string(l - 1));
}
// 固定滑窗过程
for (int right = l; right < n; ++right) {
// A1
interval_sum += nums[right];
// A2
interval_sum -= nums[right - l];
// A3
if (interval_sum <= threshold) {
// 储存的区间是左闭右闭区间,故左边界应该为right-l+1
ans.push_back(to_string(right - l + 1) + "-" + to_string(right));
}
}
if (!ans.empty()) {
string result;
for (size_t i = 0; i < ans.size(); ++i) {
if (i > 0) {
result += " ";
}
result += ans[i];
}
return result;
}
} // 如果退出循环后,没有返回任何的一个ans,则说明找不到任意一个区间满足题意
// 此时应该返回"NULL"输出
return "NULL";
} int main() {
int minAverageLost;
// 输入
cin >> minAverageLost;
cin.ignore(); // 忽略换行符 string line;
getline(cin, line);
stringstream ss(line);
vector<int> nums;
int num; while (ss >> num) {
nums.push_back(num);
if (ss.peek() == ',') {
ss.ignore();
}
} // 调用函数并输出答案
cout << solve(minAverageLost, nums) << endl; return 0;
}

时空复杂度

时间复杂度:O(N^2)。无论是固定滑窗还是前缀和算法,都需要进行双重循环。

空间复杂度:O(1)。仅需若干长度变量。

华为od机考2025A卷真题 -查找接口成功率最优时间段的更多相关文章

  1. 华为OD机试题

    """最长回文字符串问题"""# 说明:方法很多,这个是最简单,也是最容易理解的一个,利用了动态规化.# 先确定回文串的右边界i,然后以右边 ...

  2. 2020年最新阿里、字节、腾讯、京东等一线大厂高频面试(Android岗)真题合集,面试轻松无压力

    本文涵盖了阿里巴巴.腾讯.字节跳动.京东.华为等大厂的Android面试真题,不管你是要面试大厂还是普通的互联网公司,这些面试题对你肯定是有帮助的,毕竟大厂一定是行发展的标杆,很多公司的面试官同样会研 ...

  3. 软考之信息安全工程师(包含2016-2018历年真题详解+官方指定教程+VIP视频教程)

    软考-中级信息安全工程师2016-2018历年考试真题以及详细答案,同时含有信息安全工程师官方指定清华版教程.信息安全工程师高清视频教程.持续更新后续年份的资料.请点赞!!请点赞!!!绝对全部货真价实 ...

  4. PMP全真模拟题真题試題含答案解析 2019年下半年PMP考試适用 PMP中文文对照试题 【香港台灣地區PMP考試也可用】

    PMP全真模拟题真题试题 含答案解析 2019年下半年PMP考试适用 PMP中文文对照试题 [香港台灣地區PMP考試也可用]PMP全真模擬題真題試題 含答案解析 2019年下半年PMP考試适用 PMP ...

  5. 肖sir__ 代码题 ___华为od练习

    www.online1987.com 这个网站,有概率看到机考原题,后续内招,这个网站做到了原题

  6. 数百道BAT等大厂最新Python面试真题,学到你手软!

    春招临近,无论是要找工作的准毕业生,还是身在职场想要提升自己的程序员,提升自己的算法内功心法.提升 Python 编程能力,总是大有裨益的.今天,小编发现了一份好资源:Python 实现的面试题集锦! ...

  7. 华为OD两轮技术面试

    华为OD面试1性格测试选积极向上的选项,注意,性格测试也会挂人,我一个朋友性格测试就没过.2机试 一道变成题目 1h 用例60%通过即可任给一个数组,元素有20M,1T,300G之类的,其中1T=10 ...

  8. 秋招如何抱佛脚?2022最新大厂Java面试真题合集(附答案

    2022秋招眼看着就要来了,但是离谱的是,很多同学最近才想起来还有秋招这回事,所以纷纷临时抱佛脚,问我有没有什么快速磨枪的方法, 我的回答是:有! 说起来,临阵磨枪没有比背八股文更靠谱的了,很多人对这 ...

  9. 你只是看起来很努力(只是做了一遍真题,草草的对了一遍答案,然后冲出自习室继续她学生会的事情了,骗自己更容易)good——想起了自己在六大时候的无奈

    (转)你只是看起来很努力一次上课,一个女孩子垂头丧气的跟我说,老师,我考了四次四级,还没过,究竟是为什么. 我说,你真题做了吗?单词背了吗?她拿出已经翻破了的真题,跟我说,你讲的所有的题目我连答案都记 ...

  10. 第四届蓝桥杯 c/c++真题

    第四届蓝桥杯 c/c++真题 <1>高斯日记 问题 大数学家高斯有个好习惯:无论如何都要记日记. 他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210 后来人们 ...

随机推荐

  1. Kotlin:【Map集合】集合创建、集合遍历、元素增加

    to本身是一个函数

  2. Quackerjack pg walkthrough

    nmap ┌──(root㉿kali)-[~] └─# nmap -p- -A 192.168.159.57 Starting Nmap 7.94SVN ( https://nmap.org ) at ...

  3. 将个人PC转变为高效的云电脑:理论、实践与优化

    本文分享自天翼云开发者社区<将个人PC转变为高效的云电脑:理论.实践与优化>,作者:不知不觉 在数字化时代的今天,我们越来越依赖互联网和计算机技术进行工作和生活.然而,传统的个人电脑(PC ...

  4. [记录点滴] 一个Python中实现flatten的方法

    之前如果想使用flatten,一般借助于numpy.ndarray.flatten. 但是 flatten只能适用于numpy对象,即array或者mat,普通的list列表不适用. 最近找到一个轻便 ...

  5. 『Python底层原理』--CPython如何运行Python代码

    Python作为一种广泛使用的编程语言,其简洁的语法和强大的功能深受开发者喜爱. 然而,对于许多Python用户来说,CPython(Python的官方实现)的内部工作机制仍然是一个神秘的黑盒. 今天 ...

  6. Flink流处理-简单案例-01

    一.pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="htt ...

  7. SaaS+AI应用架构:业务场景、智能体、大模型、知识库、传统工具系统

    大家好,我是汤师爷~ 在SaaS与AI应用的演进过程中,合理的架构设计至关重要.本节将详细介绍其五个核心层次: 业务场景层:发现和确定业务场景 智能体层:构建可复用的智能应用 大模型层:采用最合适的大 ...

  8. IPTools for .NET:快速查询全球IP信息

    IPTools 是一个用于快速查询全球 IP 地址信息的库,支持国内和国际 IP 查询,提供详细的地理位置信息(如国家.省份.城市)以及经纬度等数据. 1. IPTools.China IPTools ...

  9. OSAL架构

    OSAL操作系统最多可以支持16个任务,由任务功耗管理PwrMgr_task_state变量可知,而OSAL每个任务最多只能支持16个事件处理,理论上最大可以执行256个事件处理. 对于一些运算能力不 ...

  10. elementUI中如何在Tabs标签页的标题文字后面添加文字或图标

    1.效果如下: 实现代码如下:<el-tab-pane name="first"> <span slot="label"> <sp ...