刷题笔记:力扣第71题-简化路径

1.拿到题目,首先想到的是遍历一遍输入路径字符串,遇到以下几种情况单独进行处理:

①遇到单独的’/’,正常读取,并记录此时的下标为上一级目录的位置。

②遇到多个连续的’/’,只读取其中一个。

③遇到’/./’,跳过对其的读取。

④遇到’/../’,跳过对其的读取,并跳转当前指针到上次记录的位置。

⑤最后判断字符串长度是否大于1并且末尾是否有’/’,如果有则删掉。

2.看起来思路十分清晰,但实际上写出来代码后一堆bug,询问了ai后,得知是本题的思路错了,应该使用栈的思想。如果用我目前的思想,会有至少如下几个问题无法解决:

①因为每次只记录上一层目录的位置,导致无法处理连续的’/../’。

②在处理’/.’和’/..’时,如果是在字符串末尾,后面没有’/’就会导致无法处理。

3.正确做法应该是先设置一个栈数组stack和栈顶指针top,数组里面用来存放每一个被’/’切开的字符串,使用strtok函数来实现这一功能,它的函数声明如下:

1. char *strtok(char *str, const char *delim);

其中str为要分割的字符串,在第一次调用时,传入要分割的字符串;后续调用时,传入 NULL,表示继续分割同一个字符串。delim为分隔符字符串,strtok()会根据这个字符串中的任意一个字符来分割str。

4.对分割完成后的字符串进行判断,如果遇到”.”则无事发生,忽略它;如果遇到”..”且栈顶指针大于0,则让栈顶指针top--;其余情况均将切割的字符串存入栈数组,然后栈顶指针top++。之后使用如下代码继续进行下一轮切割:

1. token = strtok(NULL, "/");

5.基于以上思想,完整代码如下:

 1. char* simplifyPath(char* path) {
 2.     // 1. 准备一个“桌子”(栈),用来存放有效的目录名字
 3.     // 最多也就 1000 个目录,我们开个指针数组
 4.     char* stack[1000]; 
 5.     int top = 0; // top 代表桌子上的盘子数量
 6.  
 7.     // 2. 切西瓜神器 strtok:按照 "/" 把字符串切开
 8.     // 比如 "/a//b/./.." 会被切出第一个词 "a"
 9.     char* token = strtok(path, "/");
10.  
11.     // 只要还能切出词来,就一直循环
12.     while (token != NULL) {
13.         if (strcmp(token, ".") == 0) {
14.             // 遇到 ".":什么都不做,忽略
15.         } 
16.         else if (strcmp(token, "..") == 0) {
17.             // 遇到 "..":如果桌子上有盘子,就扔掉最上面的一个(top 退一格)
18.             if (top > 0) {
19.                 top--; 
20.             }
21.         } 
22.         else {
23.             // 遇到普通的文件夹名字(哪怕是 "..."):放到桌子上(入栈)
24.             stack[top] = token;
25.             top++;
26.         }
27.         // 继续切下一个词(strtok 的固定写法,传 NULL 代表继续切刚才的字符串)
28.         token = strtok(NULL, "/");
29.     }
30.  
31.     // 3. 准备结果字符串
32.     char* res = (char*)calloc(3001, sizeof(char));
33.     
34.     // 如果桌子上一个盘子都没有,说明退到根目录了,直接返回 "/"
35.     if (top == 0) {
36.         strcpy(res, "/");
37.         return res;
38.     }
39.  
40.     // 4. 把桌子上的盘子按顺序拼起来
41.     for (int i = 0; i < top; i++) {
42.         strcat(res, "/");           // 先拼个斜杠
43.         strcat(res, stack[i]);      // 再拼上目录名
44.     }
45.  
46.     return res;
47. }

该算法时间复杂度和空间复杂度均为O(n),是本题的标准解法。

6.对于类似下面这行代码的代码:

1. strcpy(res, "/");

第二个参数”/”不能写成’/’,因为”/”的本质是字符串,是’/’ + ‘\0’,这才是char*类型的字符串指针,而strcpy的第二个参数正是char*。如果使用’/’,它本质上就是一个ASCII码数字,类型是int,会出现如下报错:

7.虽然栈思想是对的,但是我的代码不是最快的,咨询了ai后得知有如下几点问题:

①在我的代码中使用了calloc,不仅会申请内存还会将所有元素置0,不如直接使用malloc,手动在最后一位置0,这样会快一些。

②strcat函数在每次调用时,它必须从res的开头开始扫描,直到找到’ \0’为止,然后才把新内容接上去。如果已有的路径很长,那么就会导致每次循环的strcat都会需要很久。可以改成使用一个指针指向res,进行移动赋值:

1. char* p = res;
2. for (int i = 0; i < top; i++) {
3.     *p++ = '/';
4.     int l = strlen(stack[i]);
5.     memcpy(p, stack[i], l);
6.     p += l;
7. }
8. *p = '\0';

③使用字符串相关函数确实会比直接对单字符进行操作要慢。

8.在借鉴了答案后,写出的最快的纯字符操作的代码如下:

 1. char* simplifyPath(char* path) {
 2.     char* res = (char*)malloc(sizeof(char) * 3001);  // 结果栈(连续内存,最快)
 3.     int resSize = 0;  // 栈顶指针(下一个要写的位置)
 4.     int len = strlen(path);
 5.  
 6.     // ===================== 一次遍历处理所有情况 =====================
 7.     for (int i = 0; i < len; i++){
 8.  
 9.         // 1. 跳过重复斜杠 //
10.         // 如果栈最后一位是 /,现在又遇到 /,直接跳过
11.         if (i > 0 && res[resSize - 1] == '/' && path[i] == '/'){
12.             continue;
13.         }
14.  
15.         // 2. 统计连续点 . 的数量(如 . / .. / ...)
16.         int cnt = 0;
17.         if (path[i] == '.'){
18.             for (int j = i; j < len; j++){
19.                 if (path[j] == '.'){
20.                     cnt++;  // 数连续有多少个 .
21.                 } else {
22.                     break;
23.                 }
24.             }
25.         }
26.  
27.         // 3. 根据点的数量做不同处理
28.         bool back = false;
29.         // (1) 单个点 . → 代表当前目录 → 跳过
30.         if (cnt == 1 && (path[i + 1] == '/' || path[i + 1] == '\0') && res[resSize - 1] == '/'){
31.             continue;
32.         }
33.         // (2) 两个点 .. → 回退上一级目录(核心)
34.         else if (cnt == 2 && (path[i + 2] == '/' || path[i + 2] == '\0') && res[resSize - 1] == '/'){
35.             while (resSize > 1){  // 保护根目录 / 不被删
36.                 if (res[resSize - 1] == '/'){
37.                     if (back){
38.                         break;  // 找到第二个 /,停止
39.                     } else {
40.                         back = true;  // 找到第一个 /,标记
41.                     }
42.                 }
43.                 resSize--;  // 回退栈顶
44.             }
45.             i++;  // 跳过一个点
46.             continue;
47.         }
48.         // (3) 三个及以上点 ... → 当成正常目录名 → 写入栈
49.         else if (cnt > 2){
50.             for (int j = i; j < i + cnt; j++){
51.                 res[resSize++] = '.';
52.             }
53.             i += cnt - 1;  // 跳过已处理的点
54.             continue;
55.         }
56.  
57.         // 4. 正常字符(字母/数字/)→ 直接写入栈
58.         res[resSize++] = path[i];
59.     }
60.  
61.     // ===================== 最后处理 =====================
62.     // 去掉末尾多余的 /,但保留根目录 /
63.     if (resSize > 1 && res[resSize - 1] == '/'){
64.         resSize--;
65.     }
66.     res[resSize] = '\0';  // 添加结束符
67.  
68.     return res;
69. }

这说明纯字符操作也是可以实现的,不过本质上也是栈的思想,以’/’为分界进行一系列操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值