|
| 1 | +# Simplify Path |
| 2 | + |
| 3 | +You are given an absolute path for a Unix-style file system, which always begins with a slash '/'. Your task is to |
| 4 | +transform this absolute path into its simplified canonical path. |
| 5 | + |
| 6 | +The rules of a Unix-style file system are as follows: |
| 7 | + |
| 8 | +- A single period '.' represents the current directory. |
| 9 | +- A double period '..' represents the previous/parent directory. |
| 10 | +- Multiple consecutive slashes such as '//' and '///' are treated as a single slash '/'. |
| 11 | +- Any sequence of periods that does not match the rules above should be treated as a valid directory or file name. For |
| 12 | +example, '...' and '....' are valid directory or file names. |
| 13 | + |
| 14 | +The simplified canonical path should follow these rules: |
| 15 | + |
| 16 | +- The path must start with a single slash '/'. |
| 17 | +- Directories within the path must be separated by exactly one slash '/'. |
| 18 | +- The path must not end with a slash '/', unless it is the root directory. |
| 19 | +- The path must not have any single or double periods ('.' and '..') used to denote current or parent directories. |
| 20 | + |
| 21 | +Return the simplified canonical path. |
| 22 | + |
| 23 | +## Examples |
| 24 | + |
| 25 | +Example 1: |
| 26 | + |
| 27 | +```text |
| 28 | +Input: path = "/home/" |
| 29 | +
|
| 30 | +Output: "/home" |
| 31 | +
|
| 32 | +Explanation: |
| 33 | +
|
| 34 | +The trailing slash should be removed. |
| 35 | +``` |
| 36 | + |
| 37 | +Example 2: |
| 38 | +```text |
| 39 | +Input: path = "/home//foo/" |
| 40 | +
|
| 41 | +Output: "/home/foo" |
| 42 | +
|
| 43 | +Explanation: |
| 44 | +
|
| 45 | +Multiple consecutive slashes are replaced by a single one. |
| 46 | +``` |
| 47 | + |
| 48 | +Example 3: |
| 49 | + |
| 50 | +```text |
| 51 | +Input: path = "/home/user/Documents/../Pictures" |
| 52 | +
|
| 53 | +Output: "/home/user/Pictures" |
| 54 | +
|
| 55 | +Explanation: |
| 56 | +
|
| 57 | +A double period ".." refers to the directory up a level (the parent directory). |
| 58 | +``` |
| 59 | + |
| 60 | +Example 4: |
| 61 | + |
| 62 | +```text |
| 63 | +Input: path = "/../" |
| 64 | +
|
| 65 | +Output: "/" |
| 66 | +
|
| 67 | +Explanation: |
| 68 | +
|
| 69 | +Going one level up from the root directory is not possible. |
| 70 | +``` |
| 71 | + |
| 72 | +Example 5: |
| 73 | +```text |
| 74 | +Input: path = "/.../a/../b/c/../d/./" |
| 75 | +
|
| 76 | +Output: "/.../b/d" |
| 77 | +
|
| 78 | +Explanation: |
| 79 | +
|
| 80 | +"..." is a valid name for a directory in this problem. |
| 81 | +``` |
| 82 | + |
| 83 | +## Constraints |
| 84 | + |
| 85 | +- 1 <= path.length <= 3000 |
| 86 | +- path consists of English letters, digits, period '.', slash '/' or '_'. |
| 87 | +- path is a valid absolute Unix path. |
| 88 | + |
| 89 | +## Topics |
| 90 | + |
| 91 | +- String |
| 92 | +- Stack |
| 93 | + |
| 94 | +## Solution |
| 95 | + |
| 96 | +Think of navigating through directories like walking through rooms in a building. When you encounter a directory name, |
| 97 | +you enter that room (go deeper). When you see '..', you go back to the previous room (go up one level). When you see '.', |
| 98 | +you stay in the same room (no movement). |
| 99 | + |
| 100 | +The key insight is that we need to keep track of our current path as we process each component. A stack is perfect for |
| 101 | +this because: |
| 102 | + |
| 103 | +- Directory navigation is inherently stack-like: When you enter a directory, you're adding to your path (push). When you |
| 104 | + go back with '..', you're removing the last directory you entered (pop). |
| 105 | +- We only care about the final state: We don't need to preserve the original path structure - we just need to know where |
| 106 | + we end up after all the navigation commands. |
| 107 | +- Sequential processing: We can process the path from left to right, making decisions about each component independently |
| 108 | + based on simple rules. |
| 109 | + |
| 110 | +By splitting the path on '/', we get individual components that we can evaluate: |
| 111 | + |
| 112 | +- Empty strings (from consecutive slashes) and '.' don't change our position |
| 113 | +- '..' means go back (pop from stack if possible) |
| 114 | +- Everything else is a real directory name to enter (push to stack) |
| 115 | + |
| 116 | +After processing all components, the stack contains exactly the directories in our final path, in order from root to |
| 117 | +destination. Joining them with '/' and adding a leading '/' gives us the canonical path. |
| 118 | + |
| 119 | +This approach naturally handles edge cases like trying to go above root (stack is empty, so pop does nothing) and |
| 120 | +multiple consecutive slashes (they create empty strings that we skip). |
| 121 | + |
| 122 | +Step-by-step implementation: |
| 123 | + |
| 124 | +- Split the path into components: Use path.split('/') to break the path into individual directory names. This |
| 125 | + automatically handles multiple consecutive slashes by creating empty strings between them. |
| 126 | + |
| 127 | +- Initialize an empty stack: stk = [] will store the valid directory names in our final path. |
| 128 | +- Process each component: Iterate through each substring s from the split operation: |
| 129 | + - Skip empty strings and current directory: If s is empty (from consecutive slashes) or equals '.', continue to the |
| 130 | + next iteration |
| 131 | + - Handle parent directory: If s == '..': |
| 132 | + - Check if the stack is not empty before popping (we can't go above root) |
| 133 | + - If stk has elements, call stk.pop() to remove the last directory |
| 134 | + - Handle regular directories: For any other string (including '...', '....', etc.): |
| 135 | + - Push it onto the stack with stk.append(s) |
| 136 | +- Build the final path: After processing all components: |
| 137 | + - Join all elements in the stack with '/' separator: '/'.join(stk) |
| 138 | + - Add a leading '/' to ensure the path starts with root: '/' + '/'.join(stk) |
| 139 | + - This automatically handles the root directory case (empty stack returns '/') |
| 140 | + |
| 141 | +### Time and Space Complexity |
| 142 | + |
| 143 | +#### Time Complexity: O(n), where n is the length of the path string. |
| 144 | + |
| 145 | +The algorithm performs the following operations: |
| 146 | + |
| 147 | +- path.split('/'): This operation traverses the entire string once to split it by '/', which takes O(n) time. |
| 148 | +- The for loop iterates through each component produced by the split operation. In the worst case, there could be O(n) |
| 149 | + components (though typically much fewer). |
| 150 | +- Inside the loop, each operation (append, pop, string comparison) takes O(1) time for each component. |
| 151 | +- '/'.join(stk): This operation takes O(m) time where m is the total length of all strings in the stack, which is |
| 152 | + bounded by O(n) since these strings came from the original path. |
| 153 | + |
| 154 | +Overall, the time complexity is O(n) + O(n) = O(n). |
| 155 | + |
| 156 | +#### Space Complexity: O(n), where n is the length of the path string. |
| 157 | + |
| 158 | +The space usage includes: |
| 159 | + |
| 160 | +- The stk list: In the worst case, if the path contains no '..' or '.' and all valid directory names, the stack could |
| 161 | + store all components from the path, using up to O(n) space. |
| 162 | +- The result of path.split('/'): This creates a list of substrings that together can be at most O(n) characters. |
| 163 | +- The final string created by '/' + '/'.join(stk): This creates a new string of length at most O(n). |
| 164 | + |
| 165 | +Therefore, the total space complexity is O(n). |
0 commit comments