Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d5f428
week 1
alphaorderly Jun 27, 2026
3ac63f5
[alphaorderly] WEEK 02 Solutions
alphaorderly Jun 28, 2026
2536209
Merge branch 'DaleStudy:main' into main
alphaorderly Jun 28, 2026
612dbbb
fix: description에 맞게 코드 수정
alphaorderly Jun 28, 2026
a7c2098
fix: 좀 더 간결하게 수정
alphaorderly Jun 29, 2026
79f6f86
Merge branch 'DaleStudy:main' into main
alphaorderly Jul 4, 2026
3855235
[alphaorderly] WEEK 03 Solutions
alphaorderly Jul 4, 2026
33ab6bc
Merge branch 'main' of https://github.com/alphaorderly/leetcode-study
alphaorderly Jul 4, 2026
b1de0e1
fix: 불필요한 코드 삭제
alphaorderly Jul 4, 2026
f9bf16c
fix: 줄바꿈 린트 문제 수정
alphaorderly Jul 4, 2026
de3390f
[alphaorderly] WEEK 03 Solutions
alphaorderly Jul 4, 2026
b3dc7d7
Merge branch 'main' of https://github.com/alphaorderly/leetcode-study
alphaorderly Jul 4, 2026
17669b6
fix: 린트 오류 수정
alphaorderly Jul 4, 2026
d8b5903
fix: 파이썬 내장함수 사용 코드 추가
alphaorderly Jul 4, 2026
a14205b
fix: valid-palindrome 문제에 투포인터 구현 답안 추가
alphaorderly Jul 4, 2026
b087a32
fix: 로직 가독성 수정
alphaorderly Jul 5, 2026
2380e81
Merge branch 'DaleStudy:main' into main
alphaorderly Jul 10, 2026
e51ae37
[alphaorderly] WEEK 04 Solutions - draft
alphaorderly Jul 10, 2026
9592367
Merge branch 'main' of https://github.com/alphaorderly/leetcode-study
alphaorderly Jul 10, 2026
555e26d
fix: 코드 가독성 향상
alphaorderly Jul 11, 2026
9b11b3c
Merge branch 'DaleStudy:main' into main
alphaorderly Jul 17, 2026
aeffd8e
[alphaorderly] WEEK 05 Solutions
alphaorderly Jul 17, 2026
9fb334a
trie 사용한 풀이법 추가
alphaorderly Jul 19, 2026
cad3e9b
fix: 코드 체크 실패 개선
alphaorderly Jul 19, 2026
5d805dd
주석 개선
alphaorderly Jul 19, 2026
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
21 changes: 21 additions & 0 deletions best-time-to-buy-and-sell-stock/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Greedy, Two Pointers
  • 설명: 주가를 한 번의 순회로 최솟값과 최대 이익을 갱신하는 방식으로, 현재 가격과 최솟값의 차이로 이익을 업데이트합니다. 공간은 상수이고, 한 방향으로 진행하는 탐욕적 패턴에 속합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(1)

피드백: 최적의 해를 얻으려면 현재까지의 최저가와 지금의 가격 차이를 비교해 최대 이익을 업데이트한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Time Complexity: O(n)
Space Complexity: O(1)
- Single Pass approach
- Use a variable to store the least price
- Use a variable to store the maximum profit
- Update the maximum profit whenever the current price is greater than the least price
"""
class Solution:
def maxProfit(self, prices: List[int]) -> int:
N = len(prices)

least = prices[0]
ans = 0

for i in range(1, N):
ans = max(ans, prices[i] - least)
least = min(least, prices[i])

return ans
58 changes: 58 additions & 0 deletions encode-and-decode-strings/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Greedy, Dynamic Programming, Hash Map / Hash Set, Divide and Conquer, Two Pointers, Sliding Window, Fast & Slow Pointers, BFS, DFS, Backtracking, Union Find, Trie, Bit Manipulation, Heap / Priority Queue, Binary Search, Monotonic Stack, Dynamic Programming
  • 설명: 주요 아이디어는 문자열 리스트를 특정 포맷으로 인코딩/디코딩하는 방법을 구현한 점으로, 배열의 원소를 길이-prefixed 방식으로 구성하는 패턴은 문자열 분리/해석에 해당하는 비트/문자열 조작과 비슷합니다. 두 번째 구현은 길이 접두사 방식으로 디코딩하는 과정을 통해 순차적 문자열 파싱의 예를 보여줘, 문자열 파싱(해체)과 연결 과정을 포함합니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Codec.encode — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 문자열 길이 정보를 이용해 구분자로 구분하므로 일반적으로 안전한 인코딩이 가능하다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 2: Codec.decode — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 가독성과 안전성을 높인 인코딩 방식으로 동작한다. eval 제거가 핵심 개선이다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Time Complexity: O(n)
Space Complexity: O(n)
### Wrong Approach ( NEVER USE IT )
- Uses eval() on potentially untrusted input, which is a major security risk.
- If s or strs can be controlled or modified externally, arbitrary code execution is possible.
- This is for illustration only; never use eval for decoding in production code.
- Encodes by converting the list of strings to its string representation using str().
- Decodes by evaluating that string with eval(), reconstructing the original list.
"""
class Codec:
def encode(self, strs: List[str]) -> str:
return str(strs)

def decode(self, s: str) -> List[str]:
return eval(s)

"""
Time Complexity: O(n)
Space Complexity: O(n)
- Encodes the list of strings by prefixing each string with its length and a separator ("_"), then concatenating.
- Decodes by parsing each length, extracting the separated string accordingly.
- Does not use eval, so this approach is safe.
"""
class Codec:
def encode(self, strs: List[str]) -> str:
strs = [str(len(s)) + "_" + s for s in strs]
return "".join(strs)

Comment on lines +29 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 문자열 길이가 0~200 이라 고정 길이 방식도 고려해볼 수 있을 것 같습니다. 데이터 패턴에 따라 1글자가 많으면 작성해주신 방식이 유리할 거 같고 3글자가 많으면 고정길이가 유리해서 trade-off는 있을 거 같아요.
파싱하는 로직에서는 고정 자리수를 쓰는게 더 간단해서 좀 더 유리할 것 같네요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요, 고정길이 코드 하신거 보니까 훨씬 깔끔하긴 하더라구요!!

def decode(self, s: str) -> List[str]:
ans = []
N = len(s)
index = 0
size = 0

word_part = False

while index < N:
ch = s[index]
if not word_part:
if ch == "_":
if size == 0:
ans.append("")
else:
word_part = True
else:
size = (size * 10) + int(ch)
index += 1
else:
ans.append(s[index : index + size])
index += size
size = 0
word_part = False

return ans
44 changes: 44 additions & 0 deletions group-anagrams/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Hash Map / Hash Set, Divide and Conquer
  • 설명: 단어들을 키로 묶어 그룹화하는 흐름으로 해시 맵을 사용해 Anagram을 분류한다. 첫 번째 구현은 정렬된 문자열을 키로 사용하고, 두 번째 구현은 문자 빈도 수를 키로 사용해 같은 키의 단어들을 모아 리스트로 묶는다. 두 방식 모두 해시 맵 기반의 그룹화 패턴이다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n * k log k)
Space O(n * k)

피드백: 두 구현 모두 애너그램을 그룹화하는 일반적인 방법이다. 카운트 벡터를 사용하는 방식은 정렬보다 일정하게 성능을 보인다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Time Complexity: O(n * k log k)
Space Complexity: O(n * k)

- Iterate through each word in the input list
- Use a dictionary to group anagrams by their sorted tuple of characters as the key
- For each word, sort its characters and use the resulting tuple as the dictionary key
- Append the word to the appropriate list in the dictionary
- Return a list of the grouped anagrams
"""
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = defaultdict(list)

for word in strs:
d[tuple(sorted(word))].append(word)

return list(d.values())

"""
Time Complexity: O(n * k)
Space Complexity: O(n * k)

- Iterate through each word in the input list
- Use a dictionary to group anagrams; key is a tuple representing character counts
- For each word, build a character frequency tuple (instead of sorting)
- This tuple serves as a unique identifier for anagrams
- Append the word to the appropriate list in the dictionary
- Return a list of the grouped anagrams
"""
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = defaultdict(list)

def hash(s: str) -> Tuple[int, ...]:
count = [0] * 26
for ch in s:
count[ord(ch) - ord('a')] += 1
return tuple(count)

for word in strs:
d[hash(word)].append(word)

return list(d.values())
38 changes: 38 additions & 0 deletions implement-trie-prefix-tree/alphaorderly.py

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trie를 압축을 할수 있을거 같은데 좀 연구해볼 필요가 있겠네요

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Trie, Hash Map / Hash Set
  • 설명: 코드는 접두사 트리(Trie) 구조를 사용해 단어 삽입, 부분 문자열 검색, 접두사 검색을 구현합니다. 각 문자 노드를 따라가며 탐색하는 방식으로 동작하며, 중첩 그래프 형태의 트리 구조를 활용합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(L)
Space O(N)

피드백: 트라이를 사용해 글자 단위로 탐색하는 표준 접근이다. 각 노드는 자식 맵을 가지며 종료 플래그로 단어를 표시한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
Time Complexity: O(L), where L is the length of the input string (word or prefix) per operation.
Space Complexity: O(N), where N is the total number of characters inserted into the trie (all words inserted).

- Each Trie node uses a dictionary to store children nodes for each character.
- The `end` flag indicates whether the current node represents the end of a complete word.
- The `insert` method adds a word to the trie by sequentially creating/following nodes for each character.
- The `search` method checks for the presence of a full word in the trie by traversing nodes for each character and checking the `end` flag at the last character.
- The `startsWith` method checks if there exists any word in the trie with the given prefix by traversing nodes for each character in the prefix.
"""
class Trie:

def __init__(self):
self.children = dict()
self.end = False

def insert(self, word: str) -> None:
node = self

for ch in word:
if ch not in node.children:
node.children[ch] = Trie()
node = node.children[ch]

node.end = True

def search(self, word: str, isPrefix: bool = False) -> bool:
node = self

for ch in word:
if ch not in node.children:
return False
node = node.children[ch]

return True if isPrefix else node.end

def startsWith(self, prefix: str) -> bool:
return self.search(word=prefix, isPrefix=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 방법이...!

152 changes: 152 additions & 0 deletions word-break/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming, Trie, Depth-First Search
  • 설명: 코드는 단어 나눔 문제를 DP(탐색과 Memoization), Bottom-up DP, 그리고 Trie를 활용한 DFS로 해결하는 다중 구현이 혼합되어 있습니다. 첫 번째/두 번째 함수는 DP 패턴, 세 번째는 Trie 기반의 DFS로 경계 위치를 탐색합니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 3가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution.wordBreak (top-down with memoization) — Time: O(k * n^2) / Space: O(n)
복잡도
Time O(k * n^2)
Space O(n)

피드백: 메모이제이션으로 중복 탐색을 피하지만 worst-case 탐색은 여전히 비싸다. startsWith 호출은 비효율적일 수 있다.

개선 제안: 고려해볼 만한 대안: 단어 사전을 트라이에 넣고 시작 지점에서 가능한 끝 지점을 빠르게 찾는 방법으로 최악의 경우를 줄일 수 있다.

풀이 2: Solution.wordBreak (bottom-up DP) — Time: O(k * n^2) / Space: O(n)
복잡도
Time O(k * n^2)
Space O(n)

피드백: 직관적인 DP 풀이로 구현이 명확하다. 다만 inner 루프를 최적화하면 성능이 개선될 여지가 있다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 3: Trie-based DFS wordBreak — Time: O(L + n^2) / Space: O(L + n)
복잡도
Time O(L + n^2)
Space O(L + n)

피드백: 트라이를 활용해 가능한 분할 경로를 효율적으로 탐색한다. 최악의 경우 여전히 n^2의 탐색이 발생할 수 있다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
Top-down dynamic programming with memoization.

Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n
- n = len(s)
- k = len(wordDict)
- m = the maximum word length

There are at most n memoized states, and each state checks every word.
In this implementation, s[index:] also creates a suffix whose length is
O(n), while startswith may compare up to m characters.

Space Complexity: O(n)
The memoization table and recursion stack each contain at most n entries.

dp(index) returns whether the suffix beginning at index can be completely
segmented. Reaching len(s) means that all characters have been consumed.
For every dictionary word that matches the current suffix, the function
recursively checks the remaining suffix and stops at the first valid split.
"""
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
@lru_cache()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lru_cache의 maxSize 기본값이 128인데 이것도 고려하셨을까요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넹 맞아요, 문자열의 길이상 괜찮을것 같다는 판단이였어요

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넹 맞아요, 문자열의 길이상 괜찮을것 같다는 판단이였어요

@alphaorderly 128개 외에 밀려나는 캐시는 재계산하면 된다로 해석하면 될까요?
저는 cache 데코레이터 사용했는데 성능 차이가 어느정도일까요

많이 배워갑니다👍

def dp(index: int) -> bool:
if index == len(s):
return True

for word in wordDict:
if len(s) - index < len(word):
continue

if s[index:].startswith(word) and dp(index + len(word)):
return True

return False

return dp(0)

"""
Bottom-up dynamic programming over prefixes of s.

Time Complexity: O(k * n * (n + m)), or O(k * n^2) when m <= n
- n = len(s)
- k = len(wordDict)
- m = the maximum word length

Each of the n + 1 prefix boundaries checks every word. The expression
s[index - W:] creates a suffix of up to O(n) characters, and startswith
may compare up to m characters.

Space Complexity: O(n)
The dp array stores one value for every prefix boundary.

dp[index] indicates whether s[:index] can be segmented. dp[0] is true because
the empty prefix needs no words. A word of length W can end at index when the
preceding prefix, dp[index - W], is valid and the suffix beginning there starts
with that word. The final entry represents the entire string.
"""
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
S = len(s)

dp = [0] * (S + 1)
dp[0] = 1

for index in range(S + 1):
for word in wordDict:
W = len(word)
Comment on lines +61 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S, W 변수는 의미가 잘 안 드러나고 PEP 8 컨벤션이랑 달라서 가독성을 약간 떨어지게 하는 거 같습니다.

PEP 8 컨벤션에 맞추어 lowercase 를 사용하시는건 어떨까요? https://peps.python.org/pep-0008/#function-and-variable-names

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하! 저는 neetcodeIO 를 예전에 엄청 오래 보면서 공부를 했었어서 배열의 길이를 상수 취급해서 대문자로 적는거에 너무 익숙해져서 그렇긴 하네요
한번 고려해보겠습니다!

if index < W or dp[index - W] == 0:
continue
if s[index - W :].startswith(word):
dp[index] = dp[index - W]

return bool(dp[-1])

"""
Trie-assisted depth-first search over valid word boundaries.

Time Complexity: O(L + n^2)
- n = len(s)
- L = the total number of characters in wordDict

Building the trie takes O(L). Each reachable boundary is processed once,
but a trie lookup may scan the remaining suffix, giving O(n^2) total work
in the worst case.

Space Complexity: O(L + n)
The trie uses O(L) space. The visited array, DFS stack, and a lookup's list
of matching end positions use O(n) additional space.

startsWith(target, start) follows the trie from target[start:] and returns every
exclusive end index at which a dictionary word finishes. The DFS treats those
indices as the next segmentation boundaries. visited prevents the same boundary
from being added to the stack more than once.
"""
class Trie:
def __init__(self):
self.children = dict()
self.end = False

def insert(self, target: int):
node = self
Comment on lines +101 to +102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target의 타입이 잘못된 거 같습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그렇네요, 파이썬 타입 힌트는 강하게 묶여있지 않아서 실수를 자주 하게 되네요


for ch in target:
if ch not in node.children:
node.children[ch] = Trie()
node = node.children[ch]

node.end = True

def startsWith(self, target: str, start: int) -> List[int]:
ans = []
node = self

for i in range(start, len(target)):
ch = target[i]

if ch not in node.children:
return ans

node = node.children[ch]
if node.end:
ans.append(i + 1)

return ans
Comment on lines +112 to +125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ans에는 무슨 값이 담기는 걸까요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검색 대상인 target 문자열에서 start부터 보면서 trie에 들어간 문자열중에
start에서 시작하는 문자열중 prefix가 정확하게 일치하는 문자열에 한해
채웠다고 가정하고 다 채운 이후의 인덱스가 어디인지를 저장해요!

예를 들어 wordDict에 abc, teg가 있고
target이 strategy 이며 start가 4인 경우

teg가 4번 인덱스부터 시작할때 일치하기 때문에
4(t) 5(e) 6(g) 를 넘기고 그 다음 인덱스인 7이 들어갑니다.



class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
S = len(s)
t = Trie()
visited = [False] * (S + 1)

for word in wordDict:
t.insert(word)

stack = [0]

while stack:
start = stack.pop()

starts_with = t.startsWith(s, start)

for start_index in starts_with:
if start_index == S:
return True
if visited[start_index]:
continue
visited[start_index] = True
stack.append(start_index)

return False
Loading