可上 欧弟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容忍的,请各位同学认真学习,不要妄图通过其他违规途径通过考试。
题目描述与示例
题目描述
实现一个模拟目录管理功能的软件,输入一个命令序列,输出最后一条命令运行结果。
支持命令:
- 创建目录命令:
mkdir 目录名称
,如mkdir abc
为在当前目录创建abc
目录,如果已存在同名目录则不执行任何操作。此命令无输出。 - 进入目录命令:
cd 目录名称
,如cd abc
为进入abc
目录,特别地,cd ..
为返回上级目录,如果目录不存在则不执行任何操作。此命令无输出。 - 查看当前所在路径命令:
pwd
,输出当前路径字符串。
约束:
- 目录名称仅支持小写字母;
mkdir
和cd
命令的参数仅支持单个目录,如mkdir abc
和cd abc
;不支持嵌套路径和绝对路径,如mkdir abc/efg
,cd abc/efg
是不支持的。 - 目录符号为
/
,根目录/
作为初始目录。 - 任何不符合上述定义的无效命令不做任何处理并且无输出。
输入描述
输入N
行字符串,每一行字符串是一条命令
输出描述
输出最后一条命令运行结果字符串
补充说明
命令行数限制100
行以内,目录名称限制10
个字符以内
示例
输入
mkdir abc
cd abc
pwd
输出
/abc/
解题思路
系统设计的大模拟题,关键还是在于读懂题意。
对于目录的操作命令,一共有四种:创建新目录、进入已存在目录、返回上一级目录、打印当前路径。
如果你使用过Linux系统,那么上述这些概念应当是相当熟悉的。
另外,对于文件路径类的问题,类似于LC71. 简化路径 ,我们可以用一个栈path
来储存路径,一旦发生进入目录或者返回目录,则对应入栈和出栈操作。可以将path
初始化为空。
目录树形结构
创建出来的目录系统呈现一个树形结构。譬如
那么我们只需要构建这样一个树形结构来对应整个目录系统,并根据操作命令进行相应操作即可。
节点类的设计
对于每一个文件夹所对应的节点,我们需要知道以下信息:
- 这个文件夹的名字(对应打印操作)
- 这个文件夹的父节点,即它的上一级文件夹对应的节点(对应返回操作)
- 这个文件夹的子节点构成的集合,即它所有的下一级文件夹对应的节点构成的集合(对应创建、进入操作)
由于信息较多,我们可以构建出如下的节点类
class Node():
def __init__(self, name, father = None):
self.name = name
self.father = father
self.children = defaultdict(Node)
要特别注意子节点构成的集合,需要构建成哈希表的形式,其中key
为子文件名,value
为子文件对应的节点,也是一个Node
。这样做是为了方便后续的查找操作。
在整个动态模拟的过程中,我们需要知道根节点root
,还需要知道当前进入到了哪一个节点中,故需要一个当前节点curNode
,将其初始化为根节点root
。
root = Node("")
curNode = root
代码框架
对于每一个操作operations[i]
,如果
- 其为打印操作,那么存在
operations[i] == "pwd"
成立 - 其不为打印操作,那么其必然可以用空格给分割成两部分,即
op, fileName = operations[i].split()
在进一步判断操作之前,可以判断文件名fileName
本身是否存在嵌套。若其以"/"
为分割符进行切割后,长度大于1
,说明存在文件嵌套的情况,这是一个无效命令,可以直接跳过。即
if len(fileName.split("/")) > 1:
continue
若fileName
是一个有效文件名,则可以进一步op
的判断操作类型。若该操作为
- 创建操作,那么存在
op == "mkdir"
成立 - 进入操作,那么存在
op == "cd"
且fileName != ".."
成立 - 退出操作,那么存在
op == "cd"
且fileName == ".."
成立
故可以将整个模拟过程的代码框架封装在solve()
函数中,具体如下
def solve(operations):
n = len(operations)
if n == 0 or operations[-1] != "pwd":
return "/"
root = Node("")
curNode = root
path = []
for i in range(n-1):
# 打印操作
if operations[i] == "pwd":
pass
# 不是打印操作,则根据空格进行切割
op, fileName = operations[i].split()
# 文件名是否存在嵌套的判断
if len(fileName.split("/")) > 1:
continue
# 创建操作
if op == "mkdir":
pass
# 进入操作
if op == "cd" and fileName != "..":
pass
# 返回操作
if op == "cd" and fileName == "..":
pass
return "/" if len(path) == 0 else "/" + "/".join(path) + "/"
打印操作
打印操作是最简单的。由于题目要求输出最后一条命令运行结果字符串,因此如果operations
数组的最后一个元素不是打印操作,即operations[-1] != "pwd"
,则直接返回打印出"/"
即可。
n = len(operations)
if n == 0 or operations[-1] != "pwd":
return "/"
(照理来说应该返回空字符串""
,但根据考试反馈,这种情况下必须返回"/"
才是正确结果)
另外,如果在遍历过程中遇到打印操作,则直接跳过即可。
if operations[i] == "pwd":
continue
创建操作
创建操作要求在当前节点curNode
下创建一个新文件夹,我们需要判断这个子文件名fileName
是否已经存在于curNode
的子节点中。若
- 已存在,则无需重复创建,直接跳过
- 不存在,则创建新子文件对应的新节点,并且将其加入
curNode
的子节点集合中。
if op == "mkdir":
if fileName in curNode.children:
continue
newNode = Node(fileName, curNode)
curNode.children[fileName] = newNode
进入操作
进入操作要求进入当前节点curNode
下一个名为fileName
的文件夹,我们需要判断这个子文件名fileName
是否已经存在于curNode
的子节点中。若
- 不存在,则无法进入一个不存在的文件,直接跳过
- 已存在,则需要更新
curNode
为fileName
对应的节点,同时也需要将fileName
更新加入path
中
if op == "cd" and fileName != "..":
if fileName not in curNode.children:
continue
curNode = curNode.children[fileName]
path.append(fileName)
返回操作
返回操作要求返回curNode
节点的上一层节点,我们需要判断当前节点curNode
是否为根节点root
。若
- 是根节点,则无法再返回上一级,直接跳过
- 不是根节点,则我们可以通过
curNode
中的成员变量curNode.father
来得到curNode
的父节点,将curNode
进行修改,同时也需要将path
的最后一个元素(即curNode
的文件名)弹出
if op == "cd" and fileName == "..":
if curNode == root:
continue
curNode = curNode.father
path.pop()
最终结果
在做完n-1
次遍历之后,最后一次操作是打印操作,此时我们需要将path
中的结果进行合并,并且作为solve()
函数的返回值。
我们需要判断path
路径栈的长度,若
- 长度为
0
,则说明最终位于根节点,需要返回"/"
- 长度不为
0
,则需要将path
中的所有字符串用"/"
连接,并且前后还需要再加上两个"/"
,作为最终路径结果
return "/" if len(path) == 0 else "/" + "/".join(path) + "/"
代码
python
# 题目:【系统设计】2024E-模拟目录管理
# 分值:200
# 作者:许老师-闭着眼睛学数理化
# 算法:模拟/树/栈
# 代码看不懂的地方,请直接在群上提问
from collections import defaultdict
# 构建一个节点类
# 每一个节点(对应一个文件夹)都包含三个属性
# 1. 这个文件夹自己的文件名
# 2. 这个文件夹的父节点,即它的上一级文件夹对应的节点
# 3. 这个文件夹的子节点构成的集合,
# 即它所有的下一级文件夹对应的节点构成的集合
class Node():
def __init__(self, name, father = None):
self.name = name
self.father = father
self.children = defaultdict(Node)
# 解决问题的函数
def solve(operations):
# 获得操作的个数n
n = len(operations)
# 特殊情况判断,如果最后一条操作字符串不是"pwd"
# 那么最后一条命令不会产生任何输出结果
# 直接返回空字符串""
# 【但是根据同学们的反馈,这种情况下需要输出"/"才能100%完全通过】
if n == 0 or operations[-1] != "pwd":
return "/"
# 创建根节点,为空即可
root = Node("")
# 创建当前节点,用于表示
# 初始化为根节点
curNode = root
# 创建一个路径列表
path = []
# 最后一个操作必然是【打印】操作
# 遍历除了最后一个操作之外的所有其他操作
for i in range(n-1):
# 如果在中间出现【打印】操作,不影响最终结果,直接跳过
if operations[i] == "pwd":
continue
# 对当前操作,根据空格进行切割
# 分别得到具体的操作op,文件名fileName
op, fileName = operations[i].split()
# 如果文件名fileName本身存在嵌套,即以"/"为分割符进行切割后,长度大于1
# 说明这是一个无效命令,直接跳过
if len(fileName.split("/")) > 1:
continue
# 如果是一个【创建】操作
if op == "mkdir":
# 如果这个文件名已经存在于curNode的子节点哈希表中,
# 则无需重复创建,直接跳过
if fileName in curNode.children:
continue
# 创建新节点newNode,文件名为fileName,父节点为curNode
newNode = Node(fileName, curNode)
# 往当前节点curNode的子节点哈希表中,添加newNode
# key为新节点的文件名fileName,value为新节点本身newNode
curNode.children[fileName] = newNode
# 如果是一个【进入】操作
if op == "cd" and fileName != "..":
# 如果这个文件名已经尚未存在于curNode的子节点哈希表中,
# 则无法进入这个不存在的子文件夹,直接跳过
if fileName not in curNode.children:
continue
# 如果这个文件名已经在于curNode的子节点哈希表中,则可以进入
# 更新当前节点,为这个文件名为fileName的子节点
curNode = curNode.children[fileName]
# 路径列表需要加入新的文件名fileName
path.append(fileName)
# 如果是一个【返回】操作
if op == "cd" and fileName == "..":
# 如果当前节点已经是根节点,则无法再返回上一级,直接跳过
if curNode == root:
continue
# 更新当前节点,为其父节点
curNode = curNode.father
# 路径列表需要弹出最后更新的文件名
path.pop()
# 最后判断path的长度,
# 若path长度为0,说明最终返回到了根目录,应该直接输出"/"
# 否则以为连接符"/"将path列表进行拼接,前后再补上两个"/"为最终路径
return "/" if len(path) == 0 else "/" + "/".join(path) + "/"
operations = list()
# 输入未知行数,使用死循环结合try-except语句进行输入
while 1:
try:
op = input()
if op == "":
break
operations.append(op)
except:
break
# 调用solve函数并输出
print(solve(operations))
java
import java.util.*;
class Node {
String name;
Node father;
Map<String, Node> children;
Node(String name, Node father) {
this.name = name;
this.father = father;
this.children = new HashMap<>();
}
}
class Solution {
String solve(List<String> operations) {
int n = operations.size();
if (n == 0 || !operations.get(n - 1).equals("pwd")) {
return "/";
}
Node root = new Node("", null);
Node curNode = root;
List<String> path = new ArrayList<>();
for (int i = 0; i < n - 1; i++) {
if (operations.get(i).equals("pwd")) {
continue;
}
String[] parts = operations.get(i).split(" ");
String op = parts[0];
String fileName = parts[1];
if (fileName.contains("/")) {
continue;
}
if (op.equals("mkdir")) {
if (curNode.children.containsKey(fileName)) {
continue;
}
Node newNode = new Node(fileName, curNode);
curNode.children.put(fileName, newNode);
}
if (op.equals("cd") && !fileName.equals("..")) {
if (!curNode.children.containsKey(fileName)) {
continue;
}
curNode = curNode.children.get(fileName);
path.add(fileName);
}
if (op.equals("cd") && fileName.equals("..")) {
if (curNode == root) {
continue;
}
curNode = curNode.father;
path.remove(path.size() - 1);
}
}
return (path.isEmpty()) ? "/" : "/" + String.join("/", path) + "/";
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
List<String> operations = new ArrayList<>();
String op;
while (scanner.hasNextLine() && !(op = scanner.nextLine()).isEmpty()) {
operations.add(op);
}
Solution solution = new Solution();
System.out.println(solution.solve(operations));
}
}
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <sstream>
using namespace std;
class Node {
public:
string name;
Node* father;
unordered_map<string, Node*> children;
Node(string name, Node* father) : name(name), father(father) {}
};
class Solution {
public:
string solve(vector<string>& operations) {
int n = operations.size();
if (n == 0 || operations[n - 1] != "pwd") {
return "/";
}
Node* root = new Node("", nullptr);
Node* curNode = root;
vector<string> path;
for (int i = 0; i < n - 1; i++) {
if (operations[i] == "pwd") {
continue;
}
string op, fileName;
istringstream iss(operations[i]);
iss >> op >> fileName;
if (fileName.find("/") != string::npos) {
continue;
}
if (op == "mkdir") {
if (curNode->children.find(fileName) != curNode->children.end()) {
continue;
}
Node* newNode = new Node(fileName, curNode);
curNode->children[fileName] = newNode;
}
if (op == "cd" && fileName != "..") {
if (curNode->children.find(fileName) == curNode->children.end()) {
continue;
}
curNode = curNode->children[fileName];
path.push_back(fileName);
}
if (op == "cd" && fileName == "..") {
if (curNode == root) {
continue;
}
curNode = curNode->father;
path.pop_back();
}
}
return (path.empty()) ? "/" : "/" + join(path, "/") + "/";
}
string join(vector<string>& path, string delimiter) {
string result;
for (int i = 0; i < path.size(); i++) {
result += path[i];
if (i != path.size() - 1) {
result += delimiter;
}
}
return result;
}
};
int main() {
Solution sol;
vector<string> operations;
string op;
while (getline(cin, op)) {
if (op.empty()) {
break;
}
operations.push_back(op);
}
cout << sol.solve(operations) << endl;
return 0;
}
时空复杂度
时间复杂度:O(N)
。每种操作的时间复杂度均为O(1)
,一共有N
条命令。
空间复杂度:O(N)
。节点所占空间。
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
-
绿色聊天软件戳
od1336
了解更多