【LeetCode】71. Simplify Path(C++)

本文详细解析了如何简化Unix风格的文件系统中的绝对路径。通过分析LeetCode题目的背景和规则,介绍了两种实现方法,包括直接字符串操作和使用getline函数进行路径简化。文章深入探讨了各种特殊情况的处理,如多个连续的斜杠和向上移动目录的符号。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

地址:https://leetcode.com/problems/simplify-path/

题目:

Given an absolute path for a file (Unix-style), simplify it.

For example,
path = "/home/", => "/home"
path = "/a/./b/../../c/", => "/c"
path = "/a/../../b/../c//.//", => "/c"
path = "/a//b////c/d//././/..", => "/a/b/c"

In a UNIX-style file system, a period (’.’) refers to the current directory, so it can be ignored in a simplified path. Additionally, a double period ("…") moves up a directory, so it cancels out whatever the last directory was. For more information, look here: https://en.wikipedia.org/wiki/Path_(computing)#Unix_style
Corner Cases:

  • Did you consider the case where path = "/../"?
    In this case, you should return "/".
  • Another corner case is the path might contain multiple slashes '/' together, such as "/home//foo/". In this case, you should ignore redundant slashes and return "/home/foo".

理解:

按规则往下读就可以了。可以把每次读出来的文件夹记录下来,如果有..且记录不为空,则把最后的弹出来即可。

实现1:

下面是我最开始的实现,这种实现在遇到"/..."这种输入的时候有问题,我的写法读了两个…并忽略了下一个,并没有想到会有这种输入。。

class Solution {
public:
	string simplifyPath(string path) {
		vector<string> paths;
		size_t len = path.length();
		size_t curr = 0;
		while (curr < len) {
			if (path[curr] == '/') {
				++curr;
				continue;
			}
			else if (path[curr] == '.') {
				++curr;
				if (curr< len&&path[curr] == '.') {
					++curr;
					if (paths.empty())
						continue;
					paths.pop_back();
				}
			}
			else {
				string tmp;
				while (curr < len&&path[curr] != '/'&&path[curr] != '.') {
					tmp += path[curr];
					++curr;
				}
				paths.push_back(tmp);
			}
		}
		string res;
		if(paths.empty())
			res="/";
		else {
			for (auto s : paths)
				res = res + "/" + s;
		}
		return res;
	}
};

优化一下

class Solution {
public:
	string simplifyPath(string path) {
		vector<string> paths;
		size_t len = path.length();
		size_t curr = 0;
		while (curr < len) {
			while (curr < len&&path[curr] == '/')
				++curr;
			if (curr == len) break;
			int j = curr;
			while (j < len&&path[j] != '/')
				++j;
			string tmp = path.substr(curr, j - curr);
			curr = j;
			if (tmp == "..") {
				if (!paths.empty())
					paths.pop_back();
			}	
			else if(tmp!=".")
				paths.push_back(tmp);
		}
		string res;
		if (paths.empty())
			res = "/";
		else {
			for (auto s : paths)
				res = res + "/" + s;
		}
		return res;
	}
};

实现2:

这种实现利用了getline,涨姿势了,原来还能这么用。不过好慢啊。

class Solution {
public:
	string simplifyPath(string path) {
		vector<string> paths;
		string tmp;
		istringstream  in(path);
		while (getline(in, tmp, '/')) {
			if (tmp == "" || tmp == ".") continue;
			if (tmp == "..") {
				if (!paths.empty()) paths.pop_back();
			}
			else
				paths.push_back(tmp);
		}
		string res;
		if(paths.empty())
			res="/";
		else {
			for (auto s : paths)
				res = res + "/" + s;
		}
		return res;
	}
};
while (getline(ss, s, '/')) { if (s.empty() || s == ".") { continue; } 拆解这个,我不懂getline()是啥意思? 以及详细解释整个题目,让我理解:给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为 更加简洁的规范路径。 在 Unix 风格的文件系统中规则如下: 一个点 '.' 表示当前目录本身。 此外,两个点 '..' 表示将目录切换到上一级(指向父目录)。 任意多个连续的斜杠(即,'//' 或 '///')都被视为单个斜杠 '/'。 任何其他格式的点(例如,'...' 或 '....')均被视为有效的文件/目录名称。 返回的 简化路径 必须遵循下述格式: 始终以斜杠 '/' 开头。 两个目录名之间必须只有一个斜杠 '/' 。 最后一个目录名(如果存在)不能 以 '/' 结尾。 此外,路径仅包含从根目录到目标文件或目录的路径上的目录(即,不含 '.' 或 '..')。 返回简化后得到的 规范路径 。 示例 1: 输入:path = "/home/" 输出:"/home" 解释: 应删除尾随斜杠。 示例 2: 输入:path = "/home//foo/" 输出:"/home/foo" 解释: 多个连续的斜杠被单个斜杠替换。 示例 3: 输入:path = "/home/user/Documents/../Pictures" 输出:"/home/user/Pictures" 解释: 两个点 ".." 表示上一级目录(父目录)。 示例 4: 输入:path = "/../" 输出:"/" 解释: 不可能从根目录上升一级目录。 示例 5: 输入:path = "/.../a/../b/c/../d/./" 输出:"/.../b/d" 解释: "..." 在这个问题中是一个合法的目录名。 思路 把 path 用 / 分割,得到一个字符串列表。 遍历字符串列表的同时,用栈维护遍历过的字符串: 如果当前字符串是空串或者 .,什么也不做(跳过)。 如果当前字符串不是 ..,那么把字符串入栈。 否则弹出栈顶字符串(前提是栈不为空),模拟返回上一级目录。 最后把栈中字符串用 / 拼接起来(最前面也要有个 /) 提示: 1 <= path.length <= 3000 path 由英文字母,数字,'.','/' 或 '_' 组成。 path 是一个有效的 Unix 风格绝对路径。 class Solution { public: string simplifyPath(string path) { vector<string> stk; istringstream ss(path); string s; while (getline(ss, s, '/')) { if (s.empty() || s == ".") { continue; } if (s != "..") { stk.push_back(s); } else if (!stk.empty()) { stk.pop_back(); } } string ans; for (string& s : stk) { ans += '/'; ans += s; } return stk.empty() ? "/" : ans; } };
最新发布
08-02
<think>首先,用户的问题有两个部分:1. C++中getline函数的作用;2. 如何将Unix风格的绝对路径转换为规范路径,并解释代码中处理分割后的字符串的逻辑。 我需要根据系统级指令来构建我的回答: - **行内数学表达式**:使用`$...$`格式。虽然问题中没有数学内容,但如果有变量或类似的东西,我会使用它。例如,如果提到路径长度,我可以说路径长度为$n$。 - **独立公式**:使用`$$...$$`格式并单独成段。同样,这里可能不需要,因为主题是C++和路径处理。 - **LaTeX语法正确**:确保任何公式正确。 - **使用中文回答**:整个回答用中文。 - **生成相关问题**:在回答结束后,添加一个部分“§§相关问题§§”,列出3-5个相关问题。 - **引用标识**:在回答中引用的段落末尾添加引用标识,如[^1]。参考用户提供的引用[1]、[^2]、[^3],这些是关于C++ getline函数的。我需要自然地融入这些引用。 用户提供的引用: - [^1]: 关于getline在while中作为条件判断的示例代码。 - [^2]: 解释getline函数的两种重载形式。 - [^3]: 类似[^2],重复解释getline的重载。 这些引用来自之前的查询,我应该基于它们来回答getline部分。 现在,用户的具体问题: 1. **getline函数的作用**:从引用中,getline用于从输入流中读取一行文本。具体来说: - 在C++中,getline有两种常见形式:一种是用于C风格字符串(字符数组),另一种是用于std::string。 - 引用[^2]和[^3]提到:`istream& getline(char* s, streamsize n);` 和 `istream& getline(char* s, streamsize n, char delim);`,用于读取到字符数组。 - 对于std::string,有`std::getline(std::istream& is, std::string& str);` 和 `std::getline(std::istream& is, std::string& str, char delim);`。 - 作用:读取输入直到遇到换行符或指定的分隔符。 2. **Unix风格绝对路径的简化**:用户提到“将Unix风格的绝对路径转换为规范路径”。这可能是关于LeetCode或类似问题,如“Simplify Path”问题。规范路径意味着: - 移除多余的斜杠(如`//`变成`/`)。 - 处理`.`(当前目录)和`..`(上级目录)。 - 最终路径应该以`/`开头,无结尾斜杠(除非是根目录),并且是绝对路径。 - 代码逻辑:通常涉及分割路径字符串,处理每个部分。 用户要求解释“代码中处理分割后的字符串的逻辑”。所以,我需要提供一个代码示例或逻辑描述。 **回答结构**: - 先回答getline函数的作用,引用提供的资料。 - 然后解释路径简化,包括代码逻辑。 - 结构清晰,逐步解决问题。 **步骤**: 1. **介绍getline函数**: - 解释作用:用于读取一行输入。 - 引用[^1]:在while循环中作为条件判断的示例。 - 引用[^2]和[^3]:重载形式。 - 示例代码:使用std::string的版本。 2. **解释路径简化**: - 描述问题:输入一个Unix风格绝对路径(如`/home//foo/../bar`),输出规范路径(如`/home/bar`)。 - 逻辑步骤: - 分割路径字符串:使用分隔符`/`分割。 - 处理每个部分:忽略空部分和`.`;遇到`..`则弹出栈中上一个目录(如果非空)。 - 构建规范路径:用栈或列表存储有效部分,然后连接。 - 提供代码示例:用C++实现。 - 解释分割后的字符串处理:例如,使用getline或字符串流分割。 3. **生成相关问题**:在最后添加。 确保回答真实可靠:基于标准C++和常见算法。 **完整回答大纲**: 1. **C++ getline函数的作用**: - 定义和用途。 - 引用示例。 2. **Unix路径简化**: - 问题描述。 - 算法逻辑。 - 代码实现和解释。 3. **相关问题**。 现在,用中文写。</think>### C++ getline函数的作用 在C++中,`getline`函数主要用于从输入流(如标准输入`cin`或文件流)中读取一行文本,直到遇到指定的分隔符(默认为换行符`\n`)。这个函数在读取用户输入、解析文件或处理字符串时非常有用,因为它能处理包含空格的完整行,而不会像`cin >>`那样在空格处停止。`getline`有两种常见形式: - **用于C风格字符串(字符数组)**:定义在`<istream>`头文件中,语法为: ```cpp istream& getline(char* s, streamsize n); // 读取到字符数组s中,最多n个字符,遇到换行符停止 istream& getline(char* s, streamsize n, char delim); // 可指定分隔符delim ``` - **用于std::string**:定义在`<string>`头文件中,语法为: ```cpp istream& getline(istream& is, string& str); // 读取到字符串str中,默认分隔符为换行符 istream& getline(istream& is, string& str, char delim); // 可指定分隔符 ``` `getline`的返回值是输入流本身(`istream&`),这允许它在循环条件中使用(如`while(getline(cin, line))`),当读取成功时返回true,遇到文件结束或错误时返回false。这在处理连续输入行时非常高效[^1][^2][^3]。 **示例代码**:以下代码使用`getline`读取用户输入的每一行并输出。 ```cpp #include <iostream> #include <string> using namespace std; int main() { string line; while (getline(cin, line)) { // 作为循环条件,读取直到文件结束 cout << line << endl; // 输出整行 } return 0; } ``` 在这个例子中,`getline`读取`cin`输入,存储到`line`字符串中,并自动处理换行符。如果输入"Hello World",它会完整读取整个字符串,而不会在空格处截断[^1]。 ### Unix风格绝对路径的规范路径转换 在Unix系统中,绝对路径以`/`开头(如`/home/user/../docs`),但路径中可能包含多余斜杠、`.`(当前目录)或`..`(上级目录)。转换为规范路径(canonical path)的目标是简化路径,使其成为最短、无歧义的绝对形式。规范路径规则: - 移除多余斜杠(如`//`变为`/`)。 - 忽略`.`(表示当前目录,不影响路径)。 - 处理`..`(表示上级目录):如果栈非空,则弹出上一个目录部分。 - 最终路径以`/`开头,不以`/`结尾(除非是根目录`/`)。 #### 代码实现逻辑 以下是C++实现路径简化的代码,使用`getline`分割字符串并处理逻辑。核心步骤: 1. **分割路径字符串**:使用`std::istringstream`和`getline`以`/`为分隔符分割路径。 2. **处理每个部分**:遍历分割后的子字符串,忽略空字符串和`.`;遇到`..`则从栈中弹出上一个目录(如果栈非空);否则,将有效目录压入栈。 3. **构建规范路径**:将栈中目录部分用`/`连接,并添加开头的`/`。 **完整代码**: ```cpp #include <iostream> #include <string> #include <sstream> #include <vector> using namespace std; string simplifyPath(string path) { vector<string> stack; // 用向量模拟栈,存储路径部分 istringstream iss(path); // 将路径字符串转换为输入流 string part; // 步骤1: 分割字符串,使用getline以'/'为分隔符 while (getline(iss, part, '/')) { // 步骤2: 处理每个分割后的子字符串 if (part.empty() || part == ".") { continue; // 忽略空字符串和"."(当前目录) } else if (part == "..") { if (!stack.empty()) { stack.pop_back(); // 遇到"..",弹出上一个目录(栈非空时) } } else { stack.push_back(part); // 有效目录部分,压入栈 } } // 步骤3: 构建规范路径 string canonical = ""; for (const string& dir : stack) { canonical += "/" + dir; // 用"/"连接目录部分 } return canonical.empty() ? "/" : canonical; // 空栈时返回根目录 } int main() { string path = "/home//foo/../bar/"; cout << "规范路径: " << simplifyPath(path) << endl; // 输出: /home/bar return 0; } ``` #### 解释分割后字符串的处理逻辑 - **分割过程**:代码使用`istringstream`将输入路径(如`"/home//foo/../bar/"`)转换为流,然后`getline(iss, part, '/')`以`/`为分隔符分割字符串。每次调用`getline`读取一个部分,直到流结束。例如: - 路径`"/home//foo/../bar/"`被分割为:`"home"`, `""`(空), `"foo"`, `".."`, `"bar"`(注意:开头的`/`和结尾的`/`会产生空字符串)。 - **处理逻辑**: - **空字符串或`.`**:直接忽略(如分割后的`""`),因为它们不改变路径。 - **`..`**:表示返回上级目录。代码检查栈是否非空:如果非空,弹出栈顶元素(如从`["home", "foo"]`变为`["home"]`);如果栈空(如在根目录下),忽略`..`以避免错误。 - **有效目录**:压入栈(如`"home"`和`"bar"`)。 - **构建路径**:栈中元素用`/`连接,并添加开头的`/`。最终栈`["home", "bar"]`变为`"/home/bar"`。 此逻辑的时间复杂度为$O(n)$($n$为路径长度),空间复杂度为$O(n)$,适用于长路径处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值