可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳od1441了解算法冲刺训练(备注【CSDN】否则不通过)
文章目录
相关推荐阅读
- 【华为OD机考】2024D+E卷最全真题【完全原创题解 | 详细考点分类 | 不断更新题目】
- 【华为OD笔试】2024D+E卷机考套题汇总【真实反馈,不断更新,限时免费】
- 【华为OD笔试】2024D卷命题规律解读【分析300+场OD笔试考点总结】
从2024年8月14号开始,OD机考全部配置为2024E卷。
注意几个关键点:
- 大部分的题目仍为往期2023A+B+C以及2024D的旧题。注意万变不离其宗,把方法掌握,无论遇到什么题目都可以轻松应对。
- 支持多次提交题目,以最后一次提交为准。可以先做200的再做100的,然后可以反复提交。
- E卷仍然为单机位+屏幕监控的形式进行监考。
- 进入考试界面新加入了这样一段话并且用红字标出,可以看出华子对作弊代考等行为是0容忍的,请各位同学认真学习,不要妄图通过其他违规途径通过考试。

题目描述与示例
题目描述
请实现一个简易内存池,根据请求命令完成内存分配和释放。
内存池支持两种操作命令,REQUEST和RELEASE,其格式为:
REQUEST=请求的内存大小 表示请求分配指定大小内存,如果分配成功,返回分配到的内存首地址;如果内存不足,或指定的大小为0,则输出error
RELEASE=释放的内存首地址 表示释放掉之前分配的内存,释放成功无需输出,如果释放不存在的首地址则输出error。
注意:
- 内存池总大小为
100字节。 - 内存池地址分配必须是连续内存,并优先从低地址分配。
- 内存释放后可被再次分配,已释放的内存在空闲时不能被二次释放。
- 不会释放已申请的内存块的中间地址。
- 释放操作只是针对首地址所对应的单个内存块进行操作,不会影响其它内存块。
输入描述
首行为整数N,表示操作命令的个数,取值范围0<N<=100。
接下来的N行,每行将给出一个操作命令,操作命令和参数之间用"="分割。
输出描述
见题面输出要求
示例一
输入
2
REQUEST=10
REQUEST=20
输出
0
10
示例二
输入
5
REQUEST=10
REQUEST=20
RELEASE=20
RELEASE=10
REQUEST=10
输出
0
10
error
10
示例三
输入
6
REQUEST=10
REQUEST=20
RELEASE=0
REQUEST=1
REQUEST=10
REQUEST=9
输出
0
10
0
30
1
解题思路
动态模拟
本题是一道非常典型的系统设计题目。
我们需要设计一个简易****内存池,支持两种操作:
- 分配内存,对应
REQUEST操作 - 释放内存,对应
RELEASE操作
每一行的输入都对应一个操作,我们需要动态模拟该简易内存池的变化。
类似于【贪心】2023C-堆内存申请,我们可以用若干区间来表示该内存池的分配情况。譬如

可以用以下lst列表来表示内存池的占用情况
lst = [[0, 1], [3, 5], [6, 7]]
我们可以通过修改lst中的区间,来表示当前内存池的分配情况。
这些区间是左闭右开区间,如二元组[3, 5]表示从3作为起始位置,长度为2的区间,5不包含在区间内。
已知内存池的总大小为100,为了方便后续操作,我们可以在lst中固定两个不会发生修改的区间[-1, 0]和[100, 101],即上述例子修改为
lst = [[-1, 0], [0, 1], [3, 5], [6, 7], [100, 101]]
其中重要的元素是[-1, 0]中的0以及[100, 101]中的100,分别限制了整个内存池的起始和终止位置。
题目给出了n个操作,每一个操作都包含具体的操作("REQUEST"或"RELEASE")以及对应的数字num(对应分配内存的长度或释放内存的首地址),因此整个动态模拟的整体框架可以用如下方式完成
n = int(input())
operations = list()
for _ in range(n):
operations.append(input().split("="))
lst = [[-1, 0], [100, 101]]
ans = list()
for op, num in operations:
num = int(num)
# 如果是分配内存操作,调用对应的函数
if op == "REQUEST":
pass
# 如果是释放内存操作,调用对应的函数
if op == "RELEASE":
pass
for s in ans:
print(s)
剩下的事情只需要填充分配内存和释放内存的对应操作函数即可。
内存分配
内存分配的方式和【贪心】2023C-堆内存申请相似但略有不同。
申请的优先级是:在空间足够的前提下,优先从低地址分配。以下面例子为例

如果想申请长度为1的内存空间,区间[1, 3]是空间足够且最低的首地址,会在地址1处进行分配

如果想申请长度为2的内存空间,区间[1, 3]是空间足够且最低的首地址,会在地址1进行分配

如果想申请长度为3的内存空间,区间[7, 10]是空间足够且最低的首地址,会在地址7进行分配

显然我们可以通过两个相邻间隔中,前一个间隔的end,和后一个间隔的start,来得到空闲内存的情况。

对应每一次内存申请操作,我们需要做如下操作
- 特殊情况判断,判断待分配的内存长度
num是否为0,若为0则直接返回"error" - 从左往右遍历整个
lst数组 - 在每一步的遍历过程中
- 获得第
i个已分配区间[cur_start, cur_end](当前区间)和第i+1个已分配区间[nxt_start, nxt_end](下一个区间) - 计算两个区间之间的空闲内存长度
nxt_start - cur_end - 若
nxt_start - cur_end >= num,说明这个空闲内存长度足够分配一个长度为num的内存,将即将分配的这个内存块[cur_end, cur_end + num]插入到lst数组中i+1的位置 - 分配完毕后,返回该内存块的首地址
cur_end
- 获得第
- 若退出循环后仍然没有顺利分配内存,则返回
"error"
可以用将上述内存申请操作封装为request()函数,即
def request(lst, num):
if num == 0:
return "error"
for i in range(len(lst)-1):
cur_start, cur_end = lst[i]
nxt_start, nxt_end = lst[i+1]
if nxt_start - cur_end >= num:
new_interval = [cur_end, cur_end + num]
lst.insert(i+1, new_interval)
return str(cur_end)
return "error"
内存释放
内存释放的操作相对简单,我们同样需要遍历lst数组中的区间[cur_start, cur_end],当发现cur_start等于待删除的内存的首地址num的时候,则在lst中删除该区间。可以将该过程才能封装在release()函数中
def release(lst, num):
for i in range(len(lst)-1):
start, end = lst[i]
if start == num:
lst.remove([start, end])
return "done"
return "error"
代码
python
# 题目:【系统设计】2024E-简易内存池
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:模拟
# 代码看不懂的地方,请直接在群上提问
# 在内存池中分配长度为num的内存
def request(lst, num):
# 特殊情况判断,如果所分配的内存长度num为0
# 直接返回error
if num == 0:
return "error"
# 遍历lst中的所有区间
for i in range(len(lst)-1):
# 获得当前区间的起始位置,下一个区间的起始位置
cur_start, cur_end = lst[i]
nxt_start, nxt_end = lst[i+1]
# 如果下一个区间的开始位置new_start
# 减去当前区间的结束位置cur_end
# 其差值大于num1
if nxt_start - cur_end >= num:
# 说明可以紧挨着当前区间的结束位置cur_end
# 插入一个新的长度为num的区间[cur_end, cur_end+num]
new_interval = [cur_end, cur_end + num]
# 插入的位置原先的下一个区间的索引,即i+1
lst.insert(i+1, new_interval)
# 插入完毕后,返回新插入区间的地址,即cur_end
return cur_end
# 上述遍历结束后,如果没有成功插入区间,说明当前内存池没有足够的空间
# 无法分配长度为num的内存,返回error
return "error"
# 在内存池中删除首地址num的内存
def release(lst, num):
# 遍历lst中的所有区间
# 该过程也可以使用二分查找来完成
for i in range(len(lst)-1):
start, end = lst[i]
# 找到起始位置start等于所选择的首地址num的那个区间
if start == num:
# 将其在lst中删除,并且返回
lst.remove([start, end])
return "done"
# 如果在上述遍历中并没有退出函数
# 说明num并非一个存在的首地址,返回"error"
return "error"
# 输入操作数n
n = int(input())
operations = list()
# 循环n次,每行输入一个操作
for _ in range(n):
# 对于每一个输入的操作字符串
# 根据等号 "=" 进行分割,得到一个二元组
# 前一个元素为字符串"REQUEST"或者"RELEASE"
# 后一个元素为数字
operations.append(input().split("="))
# 初始化表示内存池的列表lst
# 为了使得后续的插入操作更加方便
# 初始化lst中包含两个后续不会发生修改的区间
# [-1, 0]和[100, 101]
# 分别表示该内存池的起始位置0和终止位置100
# 选取100是因为题目已经说明内存池总大小为100
lst = [[-1, 0], [100, 101]]
# 初始化答案数组
ans = list()
# 遍历operations数组中的所有op操作和对应的数字num
# 注意此时的num还是字符串类型的
for op, num in operations:
# 将num转成数字类型
num = int(num)
# 如果是分配内存操作
if op == "REQUEST":
# 调用request函数,将结果加入ans中
res = request(lst, num)
ans.append(res)
# 如果是释放内存操作
if op == "RELEASE":
# 调用release函数,如果结果是"error",将结果加入ans中
res = release(lst, num)
if res == "error":
ans.append(res)
# 输出ans中的每一行元素
for s in ans:
print(s)
java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
// 在内存池中分配长度为num的内存
static String request(List<int[]> lst, int num) {
// 特殊情况判断,如果所分配的内存长度num为0
// 直接返回error
if (num == 0) {
return "error";
}
// 遍历lst中的所有区间
for (int i = 0; i < lst.size() - 1; i++) {
// 获得当前区间的起始位置,下一个区间的起始位置
int[] curInterval = lst.get(i);
int curStart = curInterval[0];
int curEnd = curInterval[1];
int[] nextInterval = lst.get(i + 1);
int nextStart = nextInterval[0];
// 如果下一个区间的开始位置newStart
// 减去当前区间的结束位置curEnd
// 其差值大于num1
if (nextStart - curEnd >= num) {
// 说明可以紧挨着当前区间的结束位置curEnd
// 插入一个新的长度为num的区间[curEnd, curEnd+num]
int[] newInterval = {curEnd, curEnd + num};
// 插入的位置原先的下一个区间的索引,即i+1
lst.add(i + 1, newInterval);
// 插入完毕后,返回新插入区间的地址,即curEnd
return String.valueOf(curEnd);
}
}
// 上述遍历结束后,如果没有成功插入区间,说明当前内存池没有足够的空间
// 无法分配长度为num的内存,返回error
return "error";
}
// 在内存池中删除首地址num的内存
static String release(List<int[]> lst, int num) {
// 遍历lst中的所有区间
for (int i = 0; i < lst.size() - 1; i++) {
int[] interval = lst.get(i);
int start = interval[0];
// 找到起始位置start等于所选择的首地址num的那个区间
if (start == num) {
// 将其在lst中删除,并且返回
lst.remove(i);
return "done";
}
}
// 如果在上述遍历中并没有退出函数
// 说明num并非一个存在的首地址,返回"error"
return "error";
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入操作数n
int n = Integer.parseInt(scanner.nextLine());
List<int[]> lst = new ArrayList<>();
// 初始化表示内存池的列表lst
// 为了使得后续的插入操作更加方便
// 初始化lst中包含两个后续不会发生修改的区间
// [-1, 0]和[100, 101]
// 分别表示该内存池的起始位置0和终止位置100
// 选取100是因为题目已经说明内存池总大小为100
lst.add(new int[]{-1, 0});
lst.add(new int[]{100, 101});
List<String> ans = new ArrayList<>();
// 循环n次,每行输入一个操作
for (int i = 0; i < n; i++) {
// 对于每一个输入的操作字符串
// 根据等号 "=" 进行分割,得到一个二元组
// 前一个元素为字符串"REQUEST"或者"RELEASE"
// 后一个元素为数字
String[] operation = scanner.nextLine().split("=");
String op = operation[0];
int num = Integer.parseInt(operation[1]);
// 如果是分配内存操作
if (op.equals("REQUEST")) {
// 调用request函数,将结果加入ans中
String res = request(lst, num);
ans.add(res);
}
// 如果是释放内存操作
if (op.equals("RELEASE")) {
// 调用release函数,如果结果是"error",将结果加入ans中
String res = release(lst, num);
if (res.equals("error")) {
ans.add(res);
}
}
}
// 输出ans中的每一行元素
for (String s : ans) {
System.out.println(s);
}
}
}
cpp
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
// 在内存池中分配长度为num的内存
string request(vector<vector<int>>& lst, int num) {
// 特殊情况判断,如果所分配的内存长度num为0
// 直接返回error
if (num == 0) {
return "error";
}
// 遍历lst中的所有区间
for (int i = 0; i < lst.size() - 1; i++) {
// 获得当前区间的起始位置,下一个区间的起始位置
int curStart = lst[i][0];
int curEnd = lst[i][1];
int nextStart = lst[i + 1][0];
// 如果下一个区间的开始位置newStart
// 减去当前区间的结束位置curEnd
// 其差值大于num
if (nextStart - curEnd >= num) {
// 说明可以紧挨着当前区间的结束位置curEnd
// 插入一个新的长度为num的区间[curEnd, curEnd+num]
vector<int> newInterval = {curEnd, curEnd + num};
// 插入的位置原先的下一个区间的索引,即i+1
lst.insert(lst.begin() + i + 1, newInterval);
// 插入完毕后,返回新插入区间的地址,即curEnd
return to_string(curEnd);
}
}
// 上述遍历结束后,如果没有成功插入区间,说明当前内存池没有足够的空间
// 无法分配长度为num的内存,返回error
return "error";
}
// 在内存池中删除首地址num的内存
string release(vector<vector<int>>& lst, int num) {
// 遍历lst中的所有区间
for (int i = 0; i < lst.size() - 1; i++) {
int start = lst[i][0];
// 找到起始位置start等于所选择的首地址num的那个区间
if (start == num) {
// 将其在lst中删除,并且返回
lst.erase(lst.begin() + i);
return "done";
}
}
// 如果在上述遍历中并没有退出函数
// 说明num并非一个存在的首地址,返回"error"
return "error";
}
int main() {
// 输入操作数n
int n;
cin >> n;
cin.ignore(); // Ignore the newline character after n
// 初始化表示内存池的列表lst
// 为了使得后续的插入操作更加方便
// 初始化lst中包含两个后续不会发生修改的区间
// [-1, 0]和[100, 101]
// 分别表示该内存池的起始位置0和终止位置100
// 选取100是因为题目已经说明内存池总大小为100
vector<vector<int>> lst = {{-1, 0}, {100, 101}};
vector<string> ans;
// 循环n次,每行输入一个操作
for (int i = 0; i < n; i++) {
string operation;
getline(cin, operation);
// 根据等号 "=" 进行分割,得到一个二元组
// 前一个元素为字符串"REQUEST"或者"RELEASE"
// 后一个元素为数字
stringstream ss(operation);
string op;
int num;
getline(ss, op, '=');
ss >> num;
// 如果是分配内存操作
if (op == "REQUEST") {
// 调用request函数,将结果加入ans中
string res = request(lst, num);
ans.push_back(res);
}
// 如果是释放内存操作
if (op == "RELEASE") {
// 调用release函数,如果结果是"error",将结果加入ans中
string res = release(lst, num);
if (res == "error") {
ans.push_back(res);
}
}
}
// 输出ans中的每一行元素
for (string s : ans) {
cout << s << endl;
}
return 0;
}
时空复杂度
时间复杂度:O(NM)。N为操作数,M为内存池的大小,最大为100。单次分配或删除操作,都需要O(M)的时间复杂度。
空间复杂度:O(M)。为表示内存池的数组lst所占空间。
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
-
绿色聊天软件戳
od1336了解更多

2125

被折叠的 条评论
为什么被折叠?



