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
31 changes: 31 additions & 0 deletions best-time-to-buy-and-sell-stock/ICE0208.java

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
  • 설명: 주가의 최소값으로 매수하고 현재 시점의 이익을 비교하며 가장 큰 이익을 선택하는 단순한 한 방향 탐욕적 접근이다. 별도의 보조 데이터 구조 없이 순회하는 O(n) 방식으로 최적 해를 구한다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(n) O(n)
Space O(1) O(1)

피드백: 최저가와 누적 이익을 유지하며 한 번의 순회로 최댓값을 구한다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Solution {
/**
* 오늘 주식을 판매한다고 가정하면,
* 이전 날짜 중 가장 낮은 가격에 구매했을 때 최대 이익을 얻을 수 있다.
* 배열을 순회하면서 최저 가격과 최대 이익을 계속 갱신한다.
*
* 시간 복잡도: O(n)
* 공간 복잡도: O(1)
Comment on lines +2 to +8

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.

주석도 설명이 아주 잘되어있고, 코드도 정말 깔끔하네요. 수고하셨습니다!!

*/
public int maxProfit(int[] prices) {
// 현재까지 확인한 날짜 중 가장 낮은 주가
int minPrice = prices[0];

// 현재까지 얻을 수 있는 최대 이익
int maxProfit = 0;

// 첫 번째 가격은 minPrice로 사용했으므로 두 번째 가격부터 확인한다.
for (int i = 1; i < prices.length; i++) {
int currentPrice = prices[i];
int currentProfit = currentPrice - minPrice;

// 이전 최저가에 구매하고 현재 가격에 판매했을 때의 이익을 비교한다.
maxProfit = Math.max(maxProfit, currentProfit);

// 이후 날짜의 계산을 위해 지금까지의 최저가를 갱신한다.
minPrice = Math.min(minPrice, currentPrice);
}

return maxProfit;
}
}
49 changes: 49 additions & 0 deletions encode-and-decode-strings/ICE0208.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Greedy, Dynamic Programming, Hash Map / Hash Set, Divide and Conquer, Binary Search, Monotonic Stack, Heap / Priority Queue, BFS, DFS, Backtracking, Union Find, Trie, Bit Manipulation, Sliding Window, Fast & Slow Pointers, DFS, Greedy, Dynamic Programming
  • 설명: 문자열 인코딩/디코딩은 문자열의 길이와 구분자를 이용해 데이터를 직렬화하는 방식이다. 인코딩은 간단한 선형 순회로 길이와 문자열을 결합하고, 디코딩은 구분자를 따라 길이를 읽고 해당 길이의 문자열을 추출한다. 전형적으로 순차 탐색과 문자열 파싱의 패턴이다.

📊 시간/공간 복잡도 분석

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

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

피드백: 모든 문자열 길이의 합 n에 비례하는 시간과 공간 복잡도이다.

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

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

피드백: 인코딩 포맷에 맞춰 순차적으로 파싱해 복원한다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import List

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.

파이썬 3.9+ 부터는 선언을 안해도 list[str]을 쓸 수 있다고 합니다!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

앗 그렇군요
알려주셔서 감사합니다!



class Solution:

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.

어떤 문제는 자바로 푸시고, 어떤 문제는 파이썬으로 푸셨네요. 같이 연습을 하시는 이유가 따로 있나요??

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

원래 알고리즘 문제 풀이는 파이썬으로만 했는데, 최근 스프링 백엔드 쪽을 공부하고 있어서 자바와 좀 더 친해지고자 이번 알고리즘 문제는 자바로 풀고 있습니다...만! 이번 주는 시간이 없어 벼락치기로 문제를 풀다 보니 어쩔 수 없이 파이썬으로 빠르게 풀고 제출했습니다. ㅎㅎ

"""
각 문자열을 '문자열 길이#문자열' 형식으로 인코딩한다.

예:
["Hello", "World", ""]
-> "5#Hello5#World0#"

시간 복잡도: O(n)
공간 복잡도: O(n)

n은 모든 문자열 길이의 합이다.
"""

def encode(self, strs: List[str]) -> str:
encoded = []

for word in strs:
encoded.append(f"{len(word)}#{word}")

return "".join(encoded)

def decode(self, encoded_string: str) -> List[str]:
decoded = []
index = 0

while index < len(encoded_string):
delimiter_index = index

# 문자열 길이와 실제 문자열을 나누는 '#'을 찾는다.
while encoded_string[delimiter_index] != "#":
delimiter_index += 1

word_length = int(
encoded_string[index:delimiter_index]
)

word_start = delimiter_index + 1
word_end = word_start + word_length

decoded.append(encoded_string[word_start:word_end])

# 다음 문자열의 길이가 시작되는 위치로 이동한다.
index = word_end

return decoded
33 changes: 33 additions & 0 deletions group-anagrams/ICE0208.java

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, Two Pointers, Greedy, Dynamic Programming, Divide and Conquer, Backtracking, Sliding Window, Binary Search, Stack / Priority Queue, Trie, Bit Manipulation, Union Find, BFS, DFS, Monotonic Stack, Heap / Priority Queue
  • 설명: 주어진 코드는 해시맵으로 키(문자빈도 배열)를 매핑해 애너그램끼리 그룹화한다. 각 단어의 문자 빈도 계산은 고유 키를 만들고, 같은 키를 가진 단어를 같은 그룹으로 모으는 방식이다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(S) O(n * k)
Space O(n) O(n * k)

피드백: 모든 문자열을 한 번씩 처리하고 빈도 배열을 키로 사용한다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Solution {
/**
* 각 문자열의 알파벳 등장 횟수를 키로 사용해
* 같은 애너그램끼리 그룹화한다.
*
* 시간 복잡도: O(S)
* 공간 복잡도: O(n)
*
* S: 모든 문자열 길이의 합
* n: 문자열의 개수
*/
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> groups = new HashMap<>();

for (String word : strs) {
int[] frequency = new int[26];

// 알파벳별 등장 횟수를 계산한다.
for (int i = 0; i < word.length(); i++) {

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.

저는 문자열 정렬로 해결 했는데, 빈도 배열이면 시간복잡도 상 더 유리하겠네요!

frequency[word.charAt(i) - 'a']++;
}

// 빈도 배열을 같은 내용끼리 비교할 수 있는 키로 변환한다.
String key = Arrays.toString(frequency);

// 같은 키를 가진 문자열을 동일한 그룹에 추가한다.
groups.computeIfAbsent(key, ignored -> new ArrayList<>())

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.

코드 수를 줄일 수 있어서 좋네요. 배워갑니다!

.add(word);
}

return new ArrayList<>(groups.values());
}
}
51 changes: 51 additions & 0 deletions implement-trie-prefix-tree/ICE0208.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Trie, Hash Map / Hash Set
  • 설명: 주어진 코드는 Trie(접두어 트리) 구조를 구현하여 단어 삽입, 검색, 접두사 검색을 수행합니다. 자식 노드를 사전으로 관리하고, 각 노드에 단어 종료 여부를 표시하는 전형적인 Trie 패턴입니다.

📊 시간/공간 복잡도 분석

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

풀이 1: Trie.insert — Time: O(m) / Space: O(m)
복잡도
Time O(m)
Space O(m)

피드백: 새 단어를 추가할 때 길이 m에 비례하는 시간과 공간을 사용한다.

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

풀이 2: Trie.search — Time: O(m) / Space: O(1)
복잡도
Time O(m)
Space O(1)

피드백: 경로 탐색과 끝 단어 여부 확인으로 판단한다.

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

풀이 3: Trie.startsWith — Time: O(m) / Space: O(1)
복잡도
Time O(m)
Space O(1)

피드백: 접두사 여부는 경로 존재 여부로 충분하다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
class TrieNode:
def __init__(self):
# 다음 문자로 연결되는 자식 노드
self.children = {}

# 현재 노드에서 하나의 완성된 단어가 끝나는지 표시
self.is_end_of_word = False


class Trie:
def __init__(self):
# 모든 단어 탐색이 시작되는 최상위 노드
self.root = TrieNode()

def insert(self, word: str) -> None:
current_node = self.root

# 단어의 각 문자를 따라가며 경로를 생성한다.
for char in word:
if char not in current_node.children:
current_node.children[char] = TrieNode()

current_node = current_node.children[char]

# 마지막 노드에 단어의 끝임을 표시한다.
current_node.is_end_of_word = True

def search(self, word: str) -> bool:
last_node = self._find_last_node(word)

# 경로가 존재하고, 마지막 노드에서 단어가 끝나야 한다.
return (
last_node is not None
and last_node.is_end_of_word
)

def startsWith(self, prefix: str) -> bool:
# 접두사는 해당 경로가 존재하기만 하면 된다.
return self._find_last_node(prefix) is not None

def _find_last_node(self, text: str):
current_node = self.root

# 문자열의 각 문자를 따라 마지막 노드까지 이동한다.
for char in text:
if char not in current_node.children:
return None

current_node = current_node.children[char]

return current_node
26 changes: 26 additions & 0 deletions word-break/ICE0208.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, Hash Map / Hash Set, Backtracking
  • 설명: 단어 조합 여부를 재귀적으로 탐색하고, 사전 단어를 해시셋으로 빠르게 확인하며, 중간 결과를 캐시로 저장해 중복 탐색을 줄이는 패턴이 사용됩니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n^2)
Space O(n)

피드백: 동일 부분문제 재사용으로 중복 계산을 제거한다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from functools import cache


class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
word_set = set(wordDict)

@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.

굉장히 편리한 기능이네요. 중복이 바로 처리 되네요 👍🏼

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

맞아요! 메모이제이션을 자동으로 처리해줘서 알고리즘 문제 풀이할 때 유용하더라고요 ☺️

def finding(start):
# 문자열 끝까지 단어들로 나누는 데 성공한 경우
if start == len(s):
return True

# start부터 시작하는 모든 부분 문자열을 확인한다.
for end in range(start + 1, len(s) + 1):
current_word = s[start:end]

# 현재 단어가 사전에 있고,
# 나머지 문자열도 나눌 수 있다면 바로 종료한다.
if current_word in word_set and finding(end):
return True

# 어떤 방식으로도 나눌 수 없는 경우
return False

return finding(0)
Loading