【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【系统设计】2024E-简易内存池【欧弟算法】全网注释最详细分类最全的华为OD真题题解

可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1441了解算法冲刺训练(备注【CSDN】否则不通过)

相关推荐阅读

从2024年8月14号开始,OD机考全部配置为2024E卷
注意几个关键点:

  1. 大部分的题目仍为往期2023A+B+C以及2024D的旧题。注意万变不离其宗,把方法掌握,无论遇到什么题目都可以轻松应对。
  2. 支持多次提交题目,以最后一次提交为准。可以先做200的再做100的,然后可以反复提交。
  3. E卷仍然为单机位+屏幕监控的形式进行监考。
  4. 进入考试界面新加入了这样一段话并且用红字标出,可以看出华子对作弊代考等行为是0容忍的,请各位同学认真学习,不要妄图通过其他违规途径通过考试。
    在这里插入图片描述

题目描述与示例

题目描述

请实现一个简易内存池,根据请求命令完成内存分配和释放。

内存池支持两种操作命令,REQUESTRELEASE,其格式为:

REQUEST=请求的内存大小 表示请求分配指定大小内存,如果分配成功,返回分配到的内存首地址;如果内存不足,或指定的大小为0,则输出error

RELEASE=释放的内存首地址 表示释放掉之前分配的内存,释放成功无需输出,如果释放不存在的首地址则输出error

注意:

  1. 内存池总大小为100字节。
  2. 内存池地址分配必须是连续内存,并优先从低地址分配。
  3. 内存释放后可被再次分配,已释放的内存在空闲时不能被二次释放。
  4. 不会释放已申请的内存块的中间地址。
  5. 释放操作只是针对首地址所对应的单个内存块进行操作,不会影响其它内存块。

输入描述

首行为整数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

解题思路

动态模拟

本题是一道非常典型的系统设计题目。

我们需要设计一个简易****内存池,支持两种操作:

  1. 分配内存,对应REQUEST操作
  2. 释放内存,对应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,来得到空闲内存的情况。

在这里插入图片描述

对应每一次内存申请操作,我们需要做如下操作

  1. 特殊情况判断,判断待分配的内存长度num是否为0,若为0则直接返回"error"
  2. 从左往右遍历整个lst数组
  3. 在每一步的遍历过程中
    1. 获得第i个已分配区间[cur_start, cur_end](当前区间)和第i+1个已分配区间[nxt_start, nxt_end](下一个区间)
    2. 计算两个区间之间的空闲内存长度nxt_start - cur_end
    3. nxt_start - cur_end >= num,说明这个空闲内存长度足够分配一个长度为num的内存,将即将分配的这个内存块[cur_end, cur_end + num]插入到lst数组中i+1的位置
    4. 分配完毕后,返回该内存块的首地址cur_end
  4. 若退出循环后仍然没有顺利分配内存,则返回"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了解更多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闭着眼睛学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值