Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

8.23 Simplify Path

Source: src/main/kotlin/string/stack/SimplifyPath.kt Pattern: token-stack path resolution · Core page

The Problem

Canonicalize an absolute Unix path (.., ., //).

  • Constraints: n ≤ 3000.

Examples

Input:  path = "/home//foo/"     -> Output: "/home/foo"
Input:  path = "/a/./b/../../c/" -> Output: "/c"

Intuition — split on ‘/’, push names, pop on ‘..’

val tokens = path.split("/")
val stack = mutableListOf<String>()

for (token in tokens) {
    when (token) {
        "", "." -> {}
        ".." -> if (stack.isNotEmpty()) stack.removeLast()
        else -> stack.add(token)
    }
}
return "/" + stack.joinToString("/")

Approach 1 — Token-stack (the repo’s version, optimal)

class SimplifyPath {
    /**
     * @param path absolute path
     * @return     canonical path
     */
    fun simplifyPath(path: String): String {
        val tokens = path.split("/")
        val stack = mutableListOf<String>()

        for (token in tokens) {
            when (token) {
                "", "." -> {}
                ".." -> if (stack.isNotEmpty()) stack.removeLast()
                else -> stack.add(token)
            }
        }
        return "/" + stack.joinToString("/")
    }
}
import java.util.*;

public class SimplifyPath {
    /**
     * @param path absolute path
     * @return     canonical path
     */
    public String simplifyPath(String path) {
        Deque<String> stack = new ArrayDeque<>();

        for (String token : path.split("/")) {
            if (token.isEmpty() || token.equals(".")) continue;
            if (token.equals("..")) {
                if (!stack.isEmpty()) stack.pop();
            } else {
                stack.push(token);
            }
        }

        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) sb.append("/").append(stack.pollLast());
        return sb.length() == 0 ? "/" : sb.toString();
    }
}
#include <string>
#include <sstream>
#include <vector>

class SimplifyPath {
public:
    /**
     * @param path absolute path
     * @return     canonical path
     */
    std::string simplifyPath(std::string path) {
        std::vector<std::string> stack;
        std::string token;
        std::stringstream ss(path);

        while (std::getline(ss, token, '/')) {
            if (token.empty() || token == ".") continue;

            if (token == "..") {
                if (!stack.empty()) stack.pop_back();
            } else {
                stack.push_back(token);
            }
        }

        std::string result;
        for (auto& s : stack) result += "/" + s;
        return result.empty() ? "/" : result;
    }
};
def simplify_path(path: str) -> str:
    """
    @param path: absolute path
    @return:     canonical path
    """
    stack = []

    for token in path.split("/"):
        if token in ("", "."):
            continue
        if token == "..":
            if stack:
                stack.pop()
        else:
            stack.append(token)

    return "/" + "/".join(stack)
#![allow(unused)]
fn main() {
impl Solution {
    /// @param path absolute path
    /// @return     canonical path
    pub fn simplify_path(path: String) -> String {
        let mut stack: Vec<&str> = Vec::new();

        for token in path.split('/') {
            match token {
                "" | "." => {}
                ".." => { stack.pop(); }
                _ => stack.push(token),
            }
        }

        let result = stack.join("/");
        if result.is_empty() { "/".to_string() } else { format!("/{}", result) }
    }
}
}

Dry run

Input: path = "/a/./b/../../c/".

tokens: "", a, ., b, .., .., c, "".
stack: [a] -> [a,b] -> pop -> [] -> [c].
Output: "/c" ✓

Complexity

Time. One pass:

$$ T(n) = O(n) $$

Space. The stack:

$$ S(n) = O(n) $$

Variants & follow-ups

  • Interview follow-up: “Why does .. pop the last segment?” .. cancels the immediately preceding directory — the LIFO stack is exactly the path’s segment history.