71. Simplify Path(简化路径)解法(C++ & 注释)

1. 题目描述

以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。

在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径

请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。

示例 1:

输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。

示例 2:

输入:"/…/"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。

示例 3:

输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。

示例 4:

输入:"/a/./b/…/…/c/"
输出:"/c"

示例 5:

输入:"/a/…/…/b/…/c//.//"
输出:"/c"

示例 6:

输入:"/a//bc/d//././/…"
输出:"/a/b/c"

题目链接:中文题目英文题目

2. 分组处理(Process by Groups)

2.1 解题思路

笔者认为这道题从两个方面来整理总结比较合适:1)重点;2)难点。本题如果使用内置函数和额外空间,解题思路和代码比较简单不难,所以不存在太多的难点,重点就放在了几个函数的使用上面;另一方面,如果不适用任何内置函数和额外空间,那么本题在代码和思路上比较难了。所以本文先以重点入手,然后循循渐进讲解难点部分(下面"…“都为"两个点”,不知道为什么会显示成这样)。

首先我们先来明确一点简化路径的规则:

  1. 起始一定有一个’/’,两个文件夹之间有一个或多个’/’,路径最后有0~多个’/’,所以这里需要简化相邻两个文件夹只有一个’/’,末尾没有’/’,起始一个’/’;
  2. “/.” 或 “/./” 表示留在本级目录;"/…" 或 “/…/“表示返回上级目录;其余情况都表示文件夹名,比如”/…”(这里为三个点),"/.hidden";

所以根据上面的规则,我们可以把’/‘作为分隔符,两个’/'之间的内容作为一个分组(group),获取一个分组,我们按照上面的规则进行分情况处理:

  1. 如果 group == “” 或者 group == “.”,前者表示有多个连续’/’,后者表示"/.“或”/./",只需跳过继续处理即可;
  2. 如果 group == “…”,表示"/…“或”/…/"表示返回上级目录,如果前面有目录,则需要去掉;我们用一个数组存储分组的内容,如果数组长度不为0,则删除最后一个分组;
  3. 如果 group != “…”,则把group加入数组中;

以"/a/…/…/b/…/c//.//"为例,分组处理过程:

1)读取a:

--
a-

2)读取第一个和第二个"…":

--
--

3)读取b:

--
b-

4)读取第三个"…":

--
--

5)读取c:

--
c-

6)读取".":

--
c-

最后我们用’/'和每个数组的string进行拼接,如果答案string不为空,则返回答案string;反之,返回"/",表示根目录。所以整体的处理思路不难,重点介绍一下代码中遇到的两个内置函数:1)stringstream 和 2)getline;

(1)stringstream表示输入输出流,我们通过stringstream.str(const string& s)方法,把字符串s放到输入输出流中;
(2)istream& getline (istream& is, string& str, char delim)可以从输入流(istream)中读取数据,如果分隔符delim没有指定,则遇到换行符,停止读取,将之前读取的内容放在str中;如果delim被指定,则遇到指定的分隔符,停止读取,将之前读取的内容放在str中;或者当遇到文本末尾,也会自动停止读取。另外,分隔符不会被读取到str中;

到此,理解了解题思路和streastream,getline两个函数的用法,这道题的代码非常好理解,也容易写出来。此部分适用代码:2.2.1 使用内置函数和额外空间(推荐)

那么接下来我们来介绍一下不使用内置函数和额外空间的思路和方法:

因为我们不能使用额外空间,所以就不能像上面那样忽略所有’/’,等最后进行拼接了。那么,我们需要把’/‘加入到分组处理的思路之中。之前的思路不保留任何’/’,接下来的分组处理就需要保存一个’/’:

还是以上面的"/a/…/…/b/…/c//."为例,之前思路的角度:

------
abc.

现在的角度:

------
/a/…/…/b/…/c/.

所以我们以每组’/‘为起始信号,按照上面的规则进行处理。注意同样是遇到’/’,如果后面有其他内容,而不是连续’/’,所需要的操作是不一样的,所以我们初始化一个status来区别这两种情。我们也把其他情况分类到不同的status下面。

因为不使用额外空间,因而读取的数据需要写在原来的path中,所以初始化序号(idx) = 0,表示从序号0位置开始写入,也表示此时字符串为空。之后,我们根据每种情况进行写入和删除,删除操作只需要前移动idx,无需更改任何数据。下面举几个典型的例子来说明这个写入和删除操作:

例子1:"///…hi"
在这里插入图片描述
例子2:"/a/…"
在这里插入图片描述
最后完成写入和删除操作后,我们需要删除最后不需要的字符,比如上面的两个例子。此部分适用代码:2.2.2 不使用任何内置函数和额外空间

2.2 实例代码

2.2.1 使用内置函数和额外空间(推荐)

class Solution {
public:
    string simplifyPath(string path) {
        string ans, group;
        vector<string> strs;
        stringstream ss(path);

        while (getline(ss, group, '/')) {
            if (group == "" || group == ".") continue;
            // corner case: "/..",所以不能用if, else if, else
            if (group == ".." && strs.size()) strs.pop_back();
            else if (group != "..") strs.push_back(group);
        }

        for (string& str : strs) ans += "/" + str;
        return ans.size() ? ans : "/";
    }
};

2.2.2 不使用任何内置函数和额外空间

class Solution {
    void processDots(string& path, int& i, int& idx, bool& status) {
        int count = 1; // 计数的原因:如果有连续三个以上'.',则表示一个文件夹名
        i++;
        while (i < path.size() && path[i] == '.') { count++; i++; }
        i--; // 注意要返回最后一个"."的序号,因为本次循环结束还有一次i++
        if (count == 1 && (path[i + 1] == '/' || path[i + 1] == '\0')) {} // corner case: "/.hidden" - 表示文件夹名
        else if (count == 2 && (path[i + 1] == '/' || path[i + 1] == '\0')) { // corner case: "/..hidden" - 表示文件夹名
            idx--; // 去除..所属的/,即"/.." 
            // 如果前面有文件夹目录,去除;反之,只是改变status的状态
            while (idx - 1 >= 0 && path[idx - 1] != '/') idx--;
            if (idx - 1 >= 0) idx--; // 去除文件名所属的'/'
            status = true;
        }
        else {
            for (int j = 0; j < count; j++) path[idx++] = '.';
            status = true;
        }
    }

public:
    string simplifyPath(string path) {
        bool status = true;
        int idx = 0;
        for (int i = 0; i < path.size(); i++) {
            if (status) {
                if (path[i] == '/') { path[idx++] = '/'; status = false; }
                else path[idx++] = path[i];
            }
            else {
                if (path[i] == '.') processDots(path, i, idx, status);
                else if (path[i] == '/') {}
                else { path[idx++] = path[i]; status = true; }
            }
        }

        if (idx == 0 || idx == 1) return "/"; // corner case: "/a/.." or "///"
        if (path[idx - 1] == '/') idx--; // corner case: "/a/"

        int len = path.size();
        for (int i = 0; i < len - idx; i++) path.pop_back(); // 不能把path.size()写在for循环里面,因为path.size()是实时变化的
        return path;
    }
};

3. 参考资料

  1. C++ 10-lines solution
  2. stringstream常见用法介绍
  3. C++ getline函数用法详解
  4. std::stringstream::str
  5. std::stringstream
  6. std::basic_istream::getline
  7. std::getline (string)

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sympy.simplify函数是SymPy中的一个函数,用于简化数学表达式。它可以将复杂的表达式转化为更简单的形式,例如,将分数约分、将多项式因式分解等。 使用方法: 1. 导入sympy库 import sympy 2. 定义需要简化的表达式 expr = sympy.simplify(expression) 其中expression为需要简化的表达式,可以是字符串或SymPy表达式。 3. 示例 以下是一些使用sympy.simplify函数的示例: 1. 约分分数 expr = sympy.simplify("6/12") print(expr) 输出结果为: 1/2 2. 合并同类项 expr = sympy.simplify("x + 2x + 3x") print(expr) 输出结果为: 6x 3. 求导后简化 x = sympy.Symbol('x') expr = sympy.diff(sympy.sin(x)**2 + sympy.cos(x)**2, x) print(expr) expr = sympy.simplify(expr) print(expr) 输出结果为: 0 0 4. 将三角函数化简 x = sympy.Symbol('x') expr = sympy.sin(x)/sympy.cos(x) print(expr) expr = sympy.simplify(expr) print(expr) 输出结果为: tan(x) 5. 将多项式因式分解 x = sympy.Symbol('x') expr = x**2 + 2*x + 1 print(expr) expr = sympy.simplify(expr) print(expr) 输出结果为: (x + 1)**2 6. 将指数函数化简 x = sympy.Symbol('x') expr = sympy.exp(x)*sympy.exp(-x) print(expr) expr = sympy.simplify(expr) print(expr) 输出结果为: 1 7. 将对数函数化简 x = sympy.Symbol('x') expr = sympy.log(sympy.exp(x)) print(expr) expr = sympy.simplify(expr) print(expr) 输出结果为: x 总结: sympy.simplify函数可以用于简化各种类型的数学表达式,包括分数、多项式、三角函数、指数函数和对数函数等。在使用时,需要注意表达式的类型和需要简化的部分,以获得正确的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值