

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. }
这说明纯字符操作也是可以实现的,不过本质上也是栈的思想,以’/’为分界进行一系列操作。

1084

被折叠的 条评论
为什么被折叠?



