Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PYTHON_VERSION = 3.13
PROBLEM ?= find_k_closest_elements
PROBLEM ?= alien_dictionary
FORCE ?= 0
COMMA := ,

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
[![pypi](https://img.shields.io/pypi/v/leetcode-py-sdk.svg?color=blue)](https://pypi.python.org/pypi/leetcode-py-sdk)
[![downloads](https://static.pepy.tech/personalized-badge/leetcode-py-sdk?period=total&units=international_system&left_color=grey&right_color=blue&left_text=pypi%20downloads)](https://pepy.tech/projects/leetcode-py-sdk)
[![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue?logo=python)](https://github.com/wisarootl/leetcode-py/)
[![Star ⭐](https://img.shields.io/github/stars/wisarootl/leetcode-py?style=flat&logo=github&color=ffcc00)](https://github.com/wisarootl/leetcode-py)
[![Sponsor 💖](https://img.shields.io/badge/Sponsor-💖-pink?style=flat)](https://github.com/sponsors/wisarootl)

A Python package to generate professional LeetCode practice environments. Features automated problem generation from LeetCode URLs, beautiful data structure visualizations (TreeNode, ListNode, GraphNode), and comprehensive testing with 10+ test cases per problem. Built with professional development practices including CI/CD, type hints, and quality gates.

Expand Down Expand Up @@ -298,3 +300,11 @@ make gen-all-problems # Regenerate all problems (destructive)
- **CI/CD**: GitHub Actions for testing, security, pre-commit hooks, and release automation

Perfect for systematic coding interview preparation with professional development practices and enhanced debugging capabilities.

## 💖 Support This Project

[![Star ⭐](https://img.shields.io/github/stars/wisarootl/leetcode-py?style=flat&logo=github&color=ffcc00)](https://github.com/wisarootl/leetcode-py)
[![Sponsor 💖](https://img.shields.io/badge/Sponsor-💖-pink?style=flat)](https://github.com/sponsors/wisarootl)

If you find this project helpful, please consider **starring the repo ⭐** or **sponsoring my work 💖**.
Your support helps me maintain and improve this project. Thank you!
45 changes: 45 additions & 0 deletions leetcode/alien_dictionary/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Alien Dictionary

**Difficulty:** Hard
**Topics:** Array, String, Depth-First Search, Breadth-First Search, Graph, Topological Sort
**Tags:** grind-75, grind

**LeetCode:** [Problem 269](https://leetcode.com/problems/alien-dictionary/description/)

## Problem Description

There is a new alien language that uses the English alphabet. However, the order among the letters is unknown to you.

You are given a list of strings `words` from the alien language's dictionary, where the strings in `words` are **sorted lexicographically** by the rules of this new language.

Return _a string of the unique letters in the new alien language sorted in **lexicographically increasing order** by the new language's rules. If there is no solution, return_ `""`\*. If there are multiple solutions, return **any of them\***.

## Examples

### Example 1:

```
Input: words = ["wrt","wrf","er","ett","rftt"]
Output: "wertf"
```

### Example 2:

```
Input: words = ["z","x"]
Output: "zx"
```

### Example 3:

```
Input: words = ["z","x","z"]
Output: ""
Explanation: The order is invalid, so return "".
```

## Constraints

- `1 <= words.length <= 100`
- `1 <= words[i].length <= 100`
- `words[i]` consists of only lowercase English letters.
Empty file.
13 changes: 13 additions & 0 deletions leetcode/alien_dictionary/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def run_alien_order(solution_class: type, words: list[str]):
implementation = solution_class()
return implementation.alien_order(words)


def assert_alien_order(result: str, expected: str) -> bool:
if expected == "":
assert result == ""
else:
# Multiple valid solutions possible, check if result is valid
assert len(result) == len(expected)
assert set(result) == set(expected)
return True
29 changes: 29 additions & 0 deletions leetcode/alien_dictionary/playground.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.17.3
# kernelspec:
# display_name: leetcode-py-py3.13
# language: python
# name: python3
# ---

# %%
from helpers import assert_alien_order, run_alien_order
from solution import Solution

# %%
# Example test case
words = ["wrt", "wrf", "er", "ett", "rftt"]
expected = "wertf"

# %%
result = run_alien_order(Solution, words)
result

# %%
assert_alien_order(result, expected)
41 changes: 41 additions & 0 deletions leetcode/alien_dictionary/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class Solution:

# Time: O(C) where C is total number of characters in all words
# Space: O(1) since at most 26 characters in alphabet
def alien_order(self, words: list[str]) -> str:
# Build adjacency list and in-degree count
adj: dict[str, set[str]] = {c: set() for word in words for c in word}
in_degree = dict.fromkeys(adj, 0)

# Build graph by comparing adjacent words
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
min_len = min(len(w1), len(w2))

# Check for invalid case: longer word is prefix of shorter word
if len(w1) > len(w2) and w1[:min_len] == w2[:min_len]:
return ""

# Find first different character and add edge
for j in range(min_len):
if w1[j] != w2[j]:
if w2[j] not in adj[w1[j]]:
adj[w1[j]].add(w2[j])
in_degree[w2[j]] += 1
break

# Topological sort using Kahn's algorithm
queue = [c for c in in_degree if in_degree[c] == 0]
result = []

while queue:
c = queue.pop(0)
result.append(c)

for neighbor in adj[c]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)

# Check for cycle (invalid ordering)
return "".join(result) if len(result) == len(in_degree) else ""
39 changes: 39 additions & 0 deletions leetcode/alien_dictionary/test_solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import pytest

from leetcode_py import logged_test

from .helpers import assert_alien_order, run_alien_order
from .solution import Solution


class TestAlienDictionary:
def setup_method(self):
self.solution = Solution()

@logged_test
@pytest.mark.parametrize(
"words, expected",
[
(["wrt", "wrf", "er", "ett", "rftt"], "wertf"),
(["z", "x"], "zx"),
(["z", "x", "z"], ""),
(["z", "z"], "z"),
(["abc", "ab"], ""),
(["ab", "adc"], "abdc"),
(["ac", "ab", "zc", "zb"], "acbz"),
(["z"], "z"),
(["za", "zb", "ca", "cb"], "zcab"),
(["zy", "zx"], "zyx"),
(["a", "b", "ca", "cc"], "abc"),
(["abc", "bcd", "cde"], "abcde"),
(["a", "aa"], "a"),
(["ab", "abc"], "abc"),
(["abc", "ab"], ""),
(["a", "b", "c", "d"], "abcd"),
(["d", "c", "b", "a"], "dcba"),
(["ac", "ab", "b"], "acb"),
],
)
def test_alien_order(self, words: list[str], expected: str):
result = run_alien_order(Solution, words)
assert_alien_order(result, expected)
40 changes: 40 additions & 0 deletions leetcode/design_in_memory_file_system/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Design In-Memory File System

**Difficulty:** Hard
**Topics:** Design, Trie, Hash Table, String
**Tags:** grind

**LeetCode:** [Problem 588](https://leetcode.com/problems/design-in-memory-file-system/description/)

## Problem Description

Design an in-memory file system to simulate the following functions:

`ls`: Given a path in string format. If it is a file path, return a list that only contains this file's name. If it is a directory path, return the list of file and directory names **in this directory**. Your output (file and directory names together) should in **lexicographic order**.

`mkdir`: Given a **directory path** that does not exist, you should make a new directory according to the path. If the middle directories in the path don't exist either, you should create them as well. This function has void return type.

`addContentToFile`: Given a **file path** and **file content** in string format. If the file doesn't exist, you need to create that file containing given content. If the file already exists, you need to **append** given content to original content. This function has void return type.

`readContentFromFile`: Given a **file path**, return its **content** in string format.

## Examples

### Example 1:

![filesystem](https://assets.leetcode.com/uploads/2018/10/12/filesystem.png)

```
Input:
["FileSystem","ls","mkdir","addContentToFile","ls","readContentFromFile"]
[[],["/"],["a/b/c"],["/a/b/c/d","hello"],["/"],["/a/b/c/d"]]

Output:
[null,[],null,null,["a"],"hello"]
```

## Constraints

- You can assume all file or directory paths are absolute paths which begin with `/` and do not end with `/` except that the path is just `"/"`.
- You can assume that all operations will be passed valid parameters and users will not attempt to retrieve file content or list a directory or file that does not exist.
- You can assume that all directory names and file names only contain lower-case letters, and same names won't exist in the same directory.
Empty file.
33 changes: 33 additions & 0 deletions leetcode/design_in_memory_file_system/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
def run_file_system(solution_class: type, operations: list[str], inputs: list[list]):
from typing import Any

fs: Any = None
results: list[str | list[str] | None] = []
for i, operation in enumerate(operations):
if operation == "FileSystem":
fs = solution_class()
results.append(None)
elif operation == "ls":
assert fs is not None
result = fs.ls(inputs[i][0])
results.append(result)
elif operation == "mkdir":
assert fs is not None
fs.mkdir(inputs[i][0])
results.append(None)
elif operation == "addContentToFile":
assert fs is not None
fs.add_content_to_file(inputs[i][0], inputs[i][1])
results.append(None)
elif operation == "readContentFromFile":
assert fs is not None
result = fs.read_content_from_file(inputs[i][0])
results.append(result)
return results, fs


def assert_file_system(
result: list[str | list[str] | None], expected: list[str | list[str] | None]
) -> bool:
assert result == expected
return True
31 changes: 31 additions & 0 deletions leetcode/design_in_memory_file_system/playground.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.17.3
# kernelspec:
# display_name: leetcode-py-py3.13
# language: python
# name: python3
# ---

# %%
from helpers import assert_file_system, run_file_system
from solution import FileSystem

# %%
# Example test case
operations = ["FileSystem", "ls", "mkdir", "addContentToFile", "ls", "readContentFromFile"]
inputs = [[], ["/"], ["/a/b/c"], ["/a/b/c/d", "hello"], ["/"], ["/a/b/c/d"]]
expected = [None, [], None, None, ["a"], "hello"]

# %%
result, fs = run_file_system(FileSystem, operations, inputs)
print(result)
fs

# %%
assert_file_system(result, expected)
59 changes: 59 additions & 0 deletions leetcode/design_in_memory_file_system/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class FileSystem:

# Time: O(1)
# Space: O(1)
def __init__(self) -> None:
self.files: dict[str, str] = {} # path -> content
self.dirs: set[str] = set() # set of directory paths

# Time: O(N + M + K log K) where N = files, M = dirs, K = items in result
# Space: O(K) for result set and sorting
def ls(self, path: str) -> list[str]:
if path in self.files:
return [path.split("/")[-1]]

items = set()
prefix = path + "/" if path != "/" else "/"

for file_path in self.files:
if file_path.startswith(prefix):
remaining = file_path[len(prefix) :]
if remaining and "/" not in remaining:
items.add(remaining)
elif remaining and "/" in remaining:
items.add(remaining.split("/")[0])

for dir_path in self.dirs:
if dir_path.startswith(prefix):
remaining = dir_path[len(prefix) :]
if remaining and "/" not in remaining:
items.add(remaining)
elif remaining and "/" in remaining:
items.add(remaining.split("/")[0])

return sorted(items)

# Time: O(D) where D = depth of path
# Space: O(D) for path parts and directory storage
def mkdir(self, path: str) -> None:
parts = path.split("/")
for i in range(1, len(parts) + 1):
dir_path = "/".join(parts[:i])
if dir_path:
self.dirs.add(dir_path)

# Time: O(D + C) where D = depth of path, C = content length
# Space: O(D + C) for path parts and content storage
def add_content_to_file(self, file_path: str, content: str) -> None:
parts = file_path.split("/")
for i in range(1, len(parts)):
dir_path = "/".join(parts[:i])
if dir_path:
self.dirs.add(dir_path)

self.files[file_path] = self.files.get(file_path, "") + content

# Time: O(1)
# Space: O(1)
def read_content_from_file(self, file_path: str) -> str:
return self.files.get(file_path, "")
Loading