【2024年华为OD机试】(A卷,200分)- 优雅子数组 (JavaScriptJava PythonC/C++)
一、问题描述
题目描述
如果一个数组中出现次数最多的元素出现大于等于 k
次,被称为 k-优雅数组,k
也可以被称为优雅阈值。
例如:
- 数组
[1, 2, 3, 1, 2, 3, 1]
是一个 3-优雅数组,因为元素1
出现次数大于等于3
次。 - 数组
[1, 2, 3, 1, 2]
不是一个 3-优雅数组,因为其中出现次数最多的元素是1
和2
,只出现了2
次。
给定一个数组 A
和 k
,请求出 A
有多少子数组是 k-优雅子数组。
子数组是数组中一个或多个连续元素组成的数组。
例如,数组 [1, 2, 3, 4]
包含 10
个子数组,分别是:
[1]
,[1, 2]
,[1, 2, 3]
,[1, 2, 3, 4]
,[2]
,[2, 3]
,[2, 3, 4]
,[3]
,[3, 4]
,[4]
。
输入描述
- 第一行输入两个数字,以空格隔开,含义是:
A
数组长度k
值。 - 第二行输入
A
数组元素,以空格隔开。
输出描述
输出 A
有多少子数组是 k-优雅子数组。
用例
用例 1
输入:
7 3
1 2 3 1 2 3 1
输出:
1
说明:
- 只有子数组
[1, 2, 3, 1, 2, 3, 1]
是 3-优雅子数组。
用例 2
输入:
7 2
1 2 3 1 2 3 1
输出:
10
说明:
- 有多个子数组满足 2-优雅子数组 的条件。
题目解析
解题思路
-
暴力枚举法:
- 使用双指针(双重
for
循环)枚举所有子数组。 - 外层指针
i
指向子数组左边界,内层指针j
指向子数组右边界。 - 统计每个子数组内部各数字的出现次数,若某个数字出现次数大于等于
k
,则该子数组符合要求,统计结果ans++
。
- 使用双指针(双重
-
优化思路:
- 暴力枚举法的时间复杂度为
O(n^2)
,对于大规模数据会超时。 - 优化点:
- 使用 滑动窗口 的思想,减少重复统计。
- 当找到一个符合要求的子数组后,右指针
j
不需要回退,而是继续向右移动。 - 左指针
i
右移时,更新统计结果,避免重复计算。
- 暴力枚举法的时间复杂度为
-
滑动窗口优化:
- 使用一个哈希表(或数组)记录当前窗口内各数字的出现次数。
- 当某个数字的出现次数达到
k
时,记录当前窗口的贡献值(即从当前右边界到数组末尾的所有子数组都符合要求)。 - 左指针
i
右移时,更新哈希表,并检查是否仍然满足条件。
-
边界条件:
- 当左指针
i
右移时,需要减少左边界元素的计数。 - 当右指针
j
右移时,需要增加右边界元素的计数。 - 确保每次移动指针后,窗口内的统计结果正确。
- 当左指针
双指针运动逻辑
-
初始状态:
- 左指针
l = 0
,右指针r = 0
。 - 哈希表
count
记录当前窗口内各数字的出现次数。
- 左指针
-
右指针移动:
- 将右指针
r
指向的元素加入窗口,并更新哈希表。 - 若某个数字的出现次数达到
k
,则记录当前窗口的贡献值。
- 将右指针
-
左指针移动:
- 当窗口内某个数字的出现次数超过
k
时,左指针l
右移。 - 减少左边界元素的计数,并更新哈希表。
- 当窗口内某个数字的出现次数超过
-
贡献值计算:
- 当找到一个符合要求的子数组时,从当前右边界到数组末尾的所有子数组都符合要求。
- 贡献值为
n - r
,其中n
是数组长度。
示例分析
以输入 7 2
和数组 [1, 2, 3, 1, 2, 3, 1]
为例:
-
初始状态:
l = 0
,r = 0
。- 窗口为
[1]
,count = {1: 1}
。
-
右指针移动:
r = 1
,窗口为[1, 2]
,count = {1: 1, 2: 1}
。r = 2
,窗口为[1, 2, 3]
,count = {1: 1, 2: 1, 3: 1}
。r = 3
,窗口为[1, 2, 3, 1]
,count = {1: 2, 2: 1, 3: 1}
。- 此时
1
的出现次数达到2
,贡献值为7 - 3 = 4
。
-
左指针移动:
l = 1
,窗口为[2, 3, 1]
,count = {1: 1, 2: 1, 3: 1}
。r = 4
,窗口为[2, 3, 1, 2]
,count = {1: 1, 2: 2, 3: 1}
。- 此时
2
的出现次数达到2
,贡献值为7 - 4 = 3
。
-
继续移动:
- 重复上述过程,直到右指针
r
到达数组末尾。
- 重复上述过程,直到右指针
总结
- 通过滑动窗口优化,减少重复统计,时间复杂度降低到
O(n)
。 - 使用哈希表记录窗口内各数字的出现次数,确保统计结果正确。
- 每次找到符合要求的子数组后,计算贡献值并累加到结果中。
二、JavaScript算法源码
以下是代码的详细注释和讲解,帮助理解每一部分的作用和逻辑:
代码讲解
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");
// 创建 readline 接口,用于从控制台读取输入
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
// 定义数组 lines,用于存储输入的行数据
const lines = [];
// 定义变量 n 和 k,分别表示数组长度和优雅阈值
let n, k;
// 监听 'line' 事件,当用户输入一行内容时触发
rl.on("line", (line) => {
// 将输入的行数据存入 lines 数组
lines.push(line);
// 当 lines 数组中有两行数据时,表示输入完成
if (lines.length === 2) {
// 解析第一行输入,获取数组长度 n 和优雅阈值 k
[n, k] = lines[0].split(" ").map(Number);
// 解析第二行输入,获取数组 arr
const arr = lines[1].split(" ").map(Number);
// 调用 getResult 函数,计算 k-优雅子数组的数量,并输出结果
console.log(getResult(arr, n, k));
// 清空 lines 数组,以便处理下一组输入
lines.length = 0;
}
});
// 定义 getResult 函数,用于计算 k-优雅子数组的数量
function getResult(arr, n, k) {
// 初始化变量 ans,用于记录 k-优雅子数组的数量
let ans = 0;
// 初始化双指针 l 和 r,分别表示滑动窗口的左边界和右边界
let l = 0;
let r = 0;
// 定义对象 count,用于记录滑动窗口内各数字的出现次数
const count = {};
// 使用滑动窗口遍历数组
while (l < n && r < n) {
// 获取右指针 r 指向的数组元素
const c = arr[r];
// 更新当前数字 c 的出现次数
count[c] ? count[c]++ : (count[c] = 1);
// 如果当前数字 c 的出现次数达到 k 次
if (count[c] >= k) {
// 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求
ans += n - r;
// 左指针 l 右移一格,减少左边界元素的计数
count[arr[l]]--;
l++;
// 右指针 r 左移一格,回退到上一个位置
count[c]--;
r--;
}
// 右指针 r 右移一格,继续扩展窗口
r++;
}
// 返回最终找到的 k-优雅子数组的数量
return ans;
}
代码逻辑详解
-
输入处理:
- 使用
readline
模块从控制台读取输入。 - 将输入的两行数据分别解析为数组长度
n
、优雅阈值k
和数组arr
。
- 使用
-
滑动窗口初始化:
- 定义双指针
l
和r
,分别表示滑动窗口的左边界和右边界,初始值均为 0。 - 定义对象
count
,用于记录滑动窗口内各数字的出现次数。
- 定义双指针
-
滑动窗口遍历:
- 使用
while
循环遍历数组,右指针r
从 0 开始逐步向右移动。 - 对于每个右指针
r
指向的元素c
,更新其在count
中的出现次数。
- 使用
-
判断是否符合条件:
- 如果当前数字
c
的出现次数达到k
次,则:- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
n - r
。 - 左指针
l
右移一格,减少左边界元素的计数。 - 右指针
r
左移一格,回退到上一个位置。
- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
- 如果当前数字
-
右指针移动:
- 右指针
r
右移一格,继续扩展窗口。
- 右指针
-
返回结果:
- 遍历结束后,返回
ans
,即 k-优雅子数组的数量。
- 遍历结束后,返回
示例运行
以输入 7 2
和数组 [1, 2, 3, 1, 2, 3, 1]
为例:
-
初始状态:
l = 0
,r = 0
。- 窗口为
[1]
,count = {1: 1}
。
-
右指针移动:
r = 1
,窗口为[1, 2]
,count = {1: 1, 2: 1}
。r = 2
,窗口为[1, 2, 3]
,count = {1: 1, 2: 1, 3: 1}
。r = 3
,窗口为[1, 2, 3, 1]
,count = {1: 2, 2: 1, 3: 1}
。- 此时
1
的出现次数达到2
次,贡献值为7 - 3 = 4
。
-
左指针移动:
l = 1
,窗口为[2, 3, 1]
,count = {1: 1, 2: 1, 3: 1}
。r = 4
,窗口为[2, 3, 1, 2]
,count = {1: 1, 2: 2, 3: 1}
。- 此时
2
的出现次数达到2
次,贡献值为7 - 4 = 3
。
-
继续移动:
- 重复上述过程,直到右指针
r
到达数组末尾。
- 重复上述过程,直到右指针
总结
- 该代码通过滑动窗口的方法,高效地统计了数组中所有 k-优雅子数组的数量。
- 使用双指针和哈希表记录窗口内各数字的出现次数,确保统计结果正确。
- 每次找到符合要求的子数组后,计算贡献值并累加到结果中,避免了重复计算。
三、Java算法源码
以下是 Java 代码的详细注释和讲解,帮助理解每一部分的作用和逻辑:
代码讲解
import java.util.HashMap;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// 创建 Scanner 对象,用于从控制台读取输入
Scanner sc = new Scanner(System.in);
// 读取数组长度 n 和优雅阈值 k
int n = sc.nextInt();
int k = sc.nextInt();
// 定义数组 arr,用于存储输入的数组元素
Integer[] arr = new Integer[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
// 调用 getResult 方法,计算 k-优雅子数组的数量,并输出结果
System.out.println(getResult(arr, n, k));
}
public static Integer getResult(Integer[] arr, Integer n, Integer k) {
// 初始化变量 ans,用于记录 k-优雅子数组的数量
int ans = 0;
// 初始化双指针 l 和 r,分别表示滑动窗口的左边界和右边界
int l = 0;
int r = 0;
// 定义 HashMap count,用于记录滑动窗口内各数字的出现次数
HashMap<Integer, Integer> count = new HashMap<>();
// 使用滑动窗口遍历数组
while (l < n && r < n) {
// 获取右指针 r 指向的数组元素
Integer c = arr[r];
// 更新当前数字 c 的出现次数
count.put(c, count.getOrDefault(c, 0) + 1);
// 如果当前数字 c 的出现次数达到 k 次
if (count.get(c) >= k) {
// 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求
ans += n - r;
// 左指针 l 右移一格,减少左边界元素的计数
count.put(arr[l], count.get(arr[l]) - 1);
l++;
// 右指针 r 左移一格,回退到上一个位置
count.put(c, count.get(c) - 1);
r--;
}
// 右指针 r 右移一格,继续扩展窗口
r++;
}
// 返回最终找到的 k-优雅子数组的数量
return ans;
}
}
代码逻辑详解
-
输入处理:
- 使用
Scanner
从控制台读取输入的数组长度n
和优雅阈值k
。 - 读取数组
arr
的元素并存储到Integer[]
数组中。
- 使用
-
滑动窗口初始化:
- 定义双指针
l
和r
,分别表示滑动窗口的左边界和右边界,初始值均为 0。 - 定义
HashMap
对象count
,用于记录滑动窗口内各数字的出现次数。
- 定义双指针
-
滑动窗口遍历:
- 使用
while
循环遍历数组,右指针r
从 0 开始逐步向右移动。 - 对于每个右指针
r
指向的元素c
,更新其在count
中的出现次数。
- 使用
-
判断是否符合条件:
- 如果当前数字
c
的出现次数达到k
次,则:- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
n - r
。 - 左指针
l
右移一格,减少左边界元素的计数。 - 右指针
r
左移一格,回退到上一个位置。
- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
- 如果当前数字
-
右指针移动:
- 右指针
r
右移一格,继续扩展窗口。
- 右指针
-
返回结果:
- 遍历结束后,返回
ans
,即 k-优雅子数组的数量。
- 遍历结束后,返回
示例运行
以输入 7 2
和数组 [1, 2, 3, 1, 2, 3, 1]
为例:
-
初始状态:
l = 0
,r = 0
。- 窗口为
[1]
,count = {1: 1}
。
-
右指针移动:
r = 1
,窗口为[1, 2]
,count = {1: 1, 2: 1}
。r = 2
,窗口为[1, 2, 3]
,count = {1: 1, 2: 1, 3: 1}
。r = 3
,窗口为[1, 2, 3, 1]
,count = {1: 2, 2: 1, 3: 1}
。- 此时
1
的出现次数达到2
次,贡献值为7 - 3 = 4
。
-
左指针移动:
l = 1
,窗口为[2, 3, 1]
,count = {1: 1, 2: 1, 3: 1}
。r = 4
,窗口为[2, 3, 1, 2]
,count = {1: 1, 2: 2, 3: 1}
。- 此时
2
的出现次数达到2
次,贡献值为7 - 4 = 3
。
-
继续移动:
- 重复上述过程,直到右指针
r
到达数组末尾。
- 重复上述过程,直到右指针
总结
- 该代码通过滑动窗口的方法,高效地统计了数组中所有 k-优雅子数组的数量。
- 使用双指针和
HashMap
记录窗口内各数字的出现次数,确保统计结果正确。 - 每次找到符合要求的子数组后,计算贡献值并累加到结果中,避免了重复计算。
四、Python算法源码
以下是 Python 代码的详细注释和讲解,帮助理解每一部分的作用和逻辑:
代码讲解
# 输入获取
n, k = map(int, input().split()) # 读取数组长度 n 和优雅阈值 k
arr = list(map(int, input().split())) # 读取数组元素并转换为整数列表
# 算法入口
def getResult(arr, n, k):
# 初始化变量 ans,用于记录 k-优雅子数组的数量
ans = 0
# 初始化双指针 l 和 r,分别表示滑动窗口的左边界和右边界
l = 0
r = 0
# 定义字典 count,用于记录滑动窗口内各数字的出现次数
count = {}
# 使用滑动窗口遍历数组
while l < n and r < n:
# 获取右指针 r 指向的数组元素
c = arr[r]
# 如果当前数字 c 不在 count 中,则初始化为 0
if count.get(c) is None:
count[c] = 0
# 更新当前数字 c 的出现次数
count[c] += 1
# 如果当前数字 c 的出现次数达到 k 次
if count[c] >= k:
# 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求
ans += n - r
# 左指针 l 右移一格,减少左边界元素的计数
count[arr[l]] -= 1
l += 1
# 右指针 r 左移一格,回退到上一个位置
count[c] -= 1
r -= 1
# 右指针 r 右移一格,继续扩展窗口
r += 1
# 返回最终找到的 k-优雅子数组的数量
return ans
# 算法调用
print(getResult(arr, n, k))
代码逻辑详解
-
输入处理:
- 使用
input().split()
读取输入的两行数据。 - 第一行数据解析为数组长度
n
和优雅阈值k
。 - 第二行数据解析为数组
arr
,并将其转换为整数列表。
- 使用
-
滑动窗口初始化:
- 定义双指针
l
和r
,分别表示滑动窗口的左边界和右边界,初始值均为 0。 - 定义字典
count
,用于记录滑动窗口内各数字的出现次数。
- 定义双指针
-
滑动窗口遍历:
- 使用
while
循环遍历数组,右指针r
从 0 开始逐步向右移动。 - 对于每个右指针
r
指向的元素c
,更新其在count
中的出现次数。
- 使用
-
判断是否符合条件:
- 如果当前数字
c
的出现次数达到k
次,则:- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
n - r
。 - 左指针
l
右移一格,减少左边界元素的计数。 - 右指针
r
左移一格,回退到上一个位置。
- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
- 如果当前数字
-
右指针移动:
- 右指针
r
右移一格,继续扩展窗口。
- 右指针
-
返回结果:
- 遍历结束后,返回
ans
,即 k-优雅子数组的数量。
- 遍历结束后,返回
示例运行
以输入 7 2
和数组 [1, 2, 3, 1, 2, 3, 1]
为例:
-
初始状态:
l = 0
,r = 0
。- 窗口为
[1]
,count = {1: 1}
。
-
右指针移动:
r = 1
,窗口为[1, 2]
,count = {1: 1, 2: 1}
。r = 2
,窗口为[1, 2, 3]
,count = {1: 1, 2: 1, 3: 1}
。r = 3
,窗口为[1, 2, 3, 1]
,count = {1: 2, 2: 1, 3: 1}
。- 此时
1
的出现次数达到2
次,贡献值为7 - 3 = 4
。
-
左指针移动:
l = 1
,窗口为[2, 3, 1]
,count = {1: 1, 2: 1, 3: 1}
。r = 4
,窗口为[2, 3, 1, 2]
,count = {1: 1, 2: 2, 3: 1}
。- 此时
2
的出现次数达到2
次,贡献值为7 - 4 = 3
。
-
继续移动:
- 重复上述过程,直到右指针
r
到达数组末尾。
- 重复上述过程,直到右指针
总结
- 该代码通过滑动窗口的方法,高效地统计了数组中所有 k-优雅子数组的数量。
- 使用双指针和字典记录窗口内各数字的出现次数,确保统计结果正确。
- 每次找到符合要求的子数组后,计算贡献值并累加到结果中,避免了重复计算。
五、C/C++算法源码:
以下是 C++ 和 C语言 代码的中文详细注释和讲解,帮助理解每一部分的作用和逻辑:
C++ 代码
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int getResult(vector<int>& arr, int n, int k) {
int ans = 0; // 用于记录 k-优雅子数组的数量
int l = 0; // 滑动窗口的左边界
int r = 0; // 滑动窗口的右边界
unordered_map<int, int> count; // 用于记录滑动窗口内各数字的出现次数
// 使用滑动窗口遍历数组
while (l < n && r < n) {
int c = arr[r]; // 获取右指针 r 指向的数组元素
// 如果当前数字 c 不在 count 中,则初始化为 0
if (count.find(c) == count.end()) {
count[c] = 0;
}
// 更新当前数字 c 的出现次数
count[c]++;
// 如果当前数字 c 的出现次数达到 k 次
if (count[c] >= k) {
// 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求
ans += n - r;
// 左指针 l 右移一格,减少左边界元素的计数
count[arr[l]]--;
l++;
// 右指针 r 左移一格,回退到上一个位置
count[c]--;
r--;
}
// 右指针 r 右移一格,继续扩展窗口
r++;
}
// 返回最终找到的 k-优雅子数组的数量
return ans;
}
int main() {
int n, k;
cin >> n >> k; // 读取数组长度 n 和优雅阈值 k
vector<int> arr(n);
for (int i = 0; i < n; i++) {
cin >> arr[i]; // 读取数组元素
}
// 调用 getResult 函数,计算 k-优雅子数组的数量,并输出结果
cout << getResult(arr, n, k) << endl;
return 0;
}
C 语言代码
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100000 // 定义数组的最大长度
int getResult(int arr[], int n, int k) {
int ans = 0; // 用于记录 k-优雅子数组的数量
int l = 0; // 滑动窗口的左边界
int r = 0; // 滑动窗口的右边界
int count[MAX_SIZE] = {0}; // 用于记录滑动窗口内各数字的出现次数
// 使用滑动窗口遍历数组
while (l < n && r < n) {
int c = arr[r]; // 获取右指针 r 指向的数组元素
// 更新当前数字 c 的出现次数
count[c]++;
// 如果当前数字 c 的出现次数达到 k 次
if (count[c] >= k) {
// 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求
ans += n - r;
// 左指针 l 右移一格,减少左边界元素的计数
count[arr[l]]--;
l++;
// 右指针 r 左移一格,回退到上一个位置
count[c]--;
r--;
}
// 右指针 r 右移一格,继续扩展窗口
r++;
}
// 返回最终找到的 k-优雅子数组的数量
return ans;
}
int main() {
int n, k;
scanf("%d %d", &n, &k); // 读取数组长度 n 和优雅阈值 k
int arr[MAX_SIZE];
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]); // 读取数组元素
}
// 调用 getResult 函数,计算 k-优雅子数组的数量,并输出结果
printf("%d\n", getResult(arr, n, k));
return 0;
}
代码逻辑详解
-
输入处理:
- C++: 使用
cin
从控制台读取输入的数组长度n
和优雅阈值k
,以及数组arr
。 - C: 使用
scanf
从控制台读取输入的数组长度n
和优雅阈值k
,以及数组arr
。
- C++: 使用
-
滑动窗口初始化:
- 定义双指针
l
和r
,分别表示滑动窗口的左边界和右边界,初始值均为 0。 - 定义哈希表(C++ 使用
unordered_map
,C 使用数组)count
,用于记录滑动窗口内各数字的出现次数。
- 定义双指针
-
滑动窗口遍历:
- 使用
while
循环遍历数组,右指针r
从 0 开始逐步向右移动。 - 对于每个右指针
r
指向的元素c
,更新其在count
中的出现次数。
- 使用
-
判断是否符合条件:
- 如果当前数字
c
的出现次数达到k
次,则:- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
n - r
。 - 左指针
l
右移一格,减少左边界元素的计数。 - 右指针
r
左移一格,回退到上一个位置。
- 计算当前窗口的贡献值,即从当前右边界到数组末尾的所有子数组都符合要求,贡献值为
- 如果当前数字
-
右指针移动:
- 右指针
r
右移一格,继续扩展窗口。
- 右指针
-
返回结果:
- 遍历结束后,返回
ans
,即 k-优雅子数组的数量。
- 遍历结束后,返回
示例运行
以输入 7 2
和数组 [1, 2, 3, 1, 2, 3, 1]
为例:
-
初始状态:
l = 0
,r = 0
。- 窗口为
[1]
,count = {1: 1}
。
-
右指针移动:
r = 1
,窗口为[1, 2]
,count = {1: 1, 2: 1}
。r = 2
,窗口为[1, 2, 3]
,count = {1: 1, 2: 1, 3: 1}
。r = 3
,窗口为[1, 2, 3, 1]
,count = {1: 2, 2: 1, 3: 1}
。- 此时
1
的出现次数达到2
次,贡献值为7 - 3 = 4
。
-
左指针移动:
l = 1
,窗口为[2, 3, 1]
,count = {1: 1, 2: 1, 3: 1}
。r = 4
,窗口为[2, 3, 1, 2]
,count = {1: 1, 2: 2, 3: 1}
。- 此时
2
的出现次数达到2
次,贡献值为7 - 4 = 3
。
-
继续移动:
- 重复上述过程,直到右指针
r
到达数组末尾。
- 重复上述过程,直到右指针
总结
- C++ 和 C 代码逻辑一致,均通过滑动窗口的方法高效地统计了数组中所有 k-优雅子数组的数量。
- 使用双指针和哈希表(或数组)记录窗口内各数字的出现次数,确保统计结果正确。
- 每次找到符合要求的子数组后,计算贡献值并累加到结果中,避免了重复计算。
六、尾言
什么是华为OD?
华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。
为什么刷题很重要?
-
机试是进入技术面的第一关:
华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。 -
技术面试需要手撕代码:
技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。 -
入职后的可信考试:
入职华为后,还需要通过“可信考试”。可信考试分为三个等级:- 入门级:主要考察基础算法与编程能力。
- 工作级:更贴近实际业务需求,可能涉及复杂的算法或与工作内容相关的场景题目。
- 专业级:最高等级,考察深层次的算法以及优化能力,与薪资直接挂钩。
刷题策略与说明:
2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:
-
关注历年真题:
- 题库中的旧题占比较大,建议优先刷历年的A卷、B卷、C卷、D卷题目。
- 对于每道题目,建议深度理解其解题思路、代码实现,以及相关算法的适用场景。
-
适应新题目:
- E卷中包含全新题目,需要掌握全面的算法知识和一定的灵活应对能力。
- 建议关注新的刷题平台或交流群,获取最新题目的解析和动态。
-
掌握常见算法:
华为OD考试通常涉及以下算法和数据结构:- 排序算法(快速排序、归并排序等)
- 动态规划(背包问题、最长公共子序列等)
- 贪心算法
- 栈、队列、链表的操作
- 图论(最短路径、最小生成树等)
- 滑动窗口、双指针算法
-
保持编程规范:
- 注重代码的可读性和注释的清晰度。
- 熟练使用常见编程语言,如C++、Java、Python等。
如何获取资源?
-
官方参考:
- 华为招聘官网或相关的招聘平台会有一些参考信息。
- 华为OD的相关公众号可能也会发布相关的刷题资料或学习资源。
-
加入刷题社区:
- 找到可信的刷题交流群,与其他备考的小伙伴交流经验。
- 关注知名的刷题网站,如LeetCode、牛客网等,这些平台上有许多华为OD的历年真题和解析。
-
寻找系统性的教程:
- 学习一本经典的算法书籍,例如《算法导论》《剑指Offer》《编程之美》等。
- 完成系统的学习课程,例如数据结构与算法的在线课程。
积极心态与持续努力:
刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。
考试注意细节
-
本地编写代码
- 在本地 IDE(如 VS Code、PyCharm 等)上编写、保存和调试代码,确保逻辑正确后再复制粘贴到考试页面。这样可以减少语法错误,提高代码准确性。
-
调整心态,保持冷静
- 遇到提示不足或实现不确定的问题时,不必慌张,可以采用更简单或更有把握的方法替代,确保思路清晰。
-
输入输出完整性
- 注意训练和考试时都需要编写完整的输入输出代码,尤其是和题目示例保持一致。完成代码后务必及时调试,确保功能符合要求。
-
快捷键使用
- 删除行可用
Ctrl+D
,复制、粘贴和撤销分别为Ctrl+C
,Ctrl+V
,Ctrl+Z
,这些可以正常使用。 - 避免使用
Ctrl+S
,以免触发浏览器的保存功能。
- 删除行可用
-
浏览器要求
- 使用最新版的 Google Chrome 浏览器完成考试,确保摄像头开启并正常工作。考试期间不要切换到其他网站,以免影响考试成绩。
-
交卷相关
- 答题前,务必仔细查看题目示例,避免遗漏要求。
- 每完成一道题后,点击【保存并调试】按钮,多次保存和调试是允许的,系统会记录得分最高的一次结果。完成所有题目后,点击【提交本题型】按钮。
- 确保在考试结束前提交试卷,避免因未保存或调试失误而丢分。
-
时间和分数安排
- 总时间:150 分钟;总分:400 分。
- 试卷结构:2 道一星难度题(每题 100 分),1 道二星难度题(200 分)。及格分为 150 分。合理分配时间,优先完成自己擅长的题目。
-
考试环境准备
- 考试前请备好草稿纸和笔。考试中尽量避免离开座位,确保监控画面正常。
- 如需上厕所,请提前规划好时间以减少中途离开监控的可能性。
-
技术问题处理
- 如果考试中遇到断电、断网、死机等技术问题,可以关闭浏览器并重新打开试卷链接继续作答。
- 出现其他问题,请第一时间联系 HR 或监考人员进行反馈。
祝你考试顺利,取得理想成绩!