diff --git a/clone-graph/haklee.py b/clone-graph/haklee.py
new file mode 100644
index 000000000..e3edcf00c
--- /dev/null
+++ b/clone-graph/haklee.py
@@ -0,0 +1,79 @@
+"""TC: O(n + e), SC: -
+
+노드 개수 n개, 엣지 개수 e개
+
+아이디어:
+문제 설명부터가 deepcopy를 하라는 것이니 내장함수를 써서 deepcopy를 해주자.
+
+SC:
+- 내장함수가 필요한 공간들을 따로 잘 관리해주지 않을까? 아마 변수를 읽고 그대로 리턴값으로 바꿔줄듯.
+- 그렇다면 추가적으로 관리하는 공간은 필요 없다.
+
+TC:
+- deepcopy는 필요한 정보를 그대로 다 deepcopy 뜰 뿐이다. 아마 node 개수 + edge 개수에 비례해서 시간이
+  걸릴것 같다. O(n + e).
+"""
+
+"""
+# Definition for a Node.
+class Node:
+    def __init__(self, val = 0, neighbors = None):
+        self.val = val
+        self.neighbors = neighbors if neighbors is not None else []
+"""
+
+import copy
+from typing import Optional
+
+
+class Solution:
+    def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
+        return copy.deepcopy(node)
+
+
+"""TC: O(e), SC: O(e)
+
+노드 개수 n개, 엣지 개수 e개
+
+아이디어:
+dfs 돌면서 노드들을 메모해두자. neighbors에 특정 노드를 추가해야 할때 메모에 있으면 바로 가져다
+쓰고, 없으면 새로 만들어서 메모에 노드를 추가한다.
+
+SC:
+- 노드 총 n개가 memo에 올라간다. O(n).
+- 각 노드마다 neighbor가 있다. 각 edge마다 neighbor 리스트들의 총 아이템 개수에 2개씩 기여한다. O(e).
+- 더하면 O(n + e). 즉, 둘 중 더 큰 값이 공간복잡도를 지배한다.
+  ...고 생각하는 것이 일차적인 분석인데, 여기서 더 나아갈 수 있다.
+- 주어진 조건에 따르면 우리에게 주어진 그래프는 connected graph다. 즉, 엣지 개수가 n-1개 이상이다.
+- 즉, O(n) < O(e)가 무조건 성립하므로, O(e) < O(n + e) < O(e + e) = O(e). 즉, O(e).
+
+TC:
+- SC와 비슷한 방식으로 분석 가능. O(e).
+"""
+
+"""
+# Definition for a Node.
+class Node:
+    def __init__(self, val = 0, neighbors = None):
+        self.val = val
+        self.neighbors = neighbors if neighbors is not None else []
+"""
+
+from typing import Optional
+
+
+class Solution:
+    def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:
+        if node is None:
+            return node
+
+        memo = {}
+
+        def dfs(node):
+            if node not in memo:
+                new_node = Node(node.val, [])
+                memo[node] = new_node
+                new_node.neighbors = [dfs(i) for i in node.neighbors]
+            return memo[node]
+
+        return dfs(node)
diff --git a/longest-common-subsequence/haklee.py b/longest-common-subsequence/haklee.py
new file mode 100644
index 000000000..2d384d929
--- /dev/null
+++ b/longest-common-subsequence/haklee.py
@@ -0,0 +1,64 @@
+"""TC: O(m * n), SC: O(m * n)
+
+주어진 문자열의 길이를 각각 m, n이라고 하자.
+
+아이디어:
+- 두 문자열이 주어졌는데 끝이 같은 문자라고 하자. 이 경우 lcs의 길이는 각각의 문자열에서
+  끝 문자를 제거한 문자열로 lcs의 길이를 구한 값에 1을 더한 값이다.
+    - e.g.) abcz, bcdefz의 lcs 길이를 `x`라고 한다면,
+            abc/z, bcdef/z에서 끝의 z가 같은 문자니까 이게 lcs에 들어간다 칠 수 있으므로,
+            abc, bcdef의 lcs 길이는 `x - 1`이 된다.
+- 두 문자열의 끝 문자가 다를 경우, 첫 번째 문자열의 끝 문자를 제거하고 구한 lcs의 길이나
+  두 번째 문자열의 끝 문자를 제고하고 구한 lcs의 길이 둘 중 큰 값이 원래 문자열로 구한 lcs
+  의 길이다.
+    - e.g.) abcz, bcdefy의 lcs 길이를 `x`라고 한다면,
+            abc, bcdefy의 lcs 길이와
+            abcz, bcdef의 lcs 길이
+            둘 중 더 큰 값을 취하면 된다.
+- LCS는 유명한 알고리즘이므로 위의 설명을 시각적으로 잘 표현한 예시들을 온라인상에서 쉽게
+  찾을 수 있다.
+- 위의 아이디어를 점화식으로 바꾸면
+    - 첫 번째 문자열의 앞 i글자로 만든 문자열과 두 번째 문자열의 앞 j글자로 만든 문자열의
+      lcs의 길이를 lcs(i, j)라고 하자.
+    - 첫 번째 문자열의 i번째 글자와 두 번째 문자열의 j번째 글자가 같은 경우 다음의 식이 성립.
+        - lcs(i, j) = lcs(i-1, j-1) + 1
+    - 다를 경우, 다음의 식이 성립.
+        - lcs(i, j) = max(lcs(i-1, j), lcs(i, j-1))
+- 위의 아이디어를 memoize를 하는 dp를 통해 구현할 수 있다. 자세한 내용은 코드 참조.
+
+SC:
+- 첫 번째 문자열의 앞 i글자로 만든 문자열과 두 번째 문자열의 앞 j글자로 만든 문자열의 lcs의
+  길이를 관리.
+- 그런데 아이디어에 제시된 점화식을 보면 i, j값에 대한 전체 배열을 저장할 필요 없이 i=k일때
+  값을 구하려 한다면 i=k-1일때 구한 lcs값만 알고 있으면 충분하다.
+- 즉, 배열은 현재 구하고자 하는 i값에 대한 j개의 아이템과 직전에 구한 j개의 아이템만 저장하면
+  충분하다. 즉, text2의 길이인 O(n)이라고 볼 수 있다.
+- 그런데 만약 text2의 길이가 text1보다 길면 이 둘을 바꿔치기해서 위의 공간복잡도를 O(m)이라고
+  봐도 아이디어 자체는 똑같이 작동하지 않는가?
+- 즉, O(min(m, n))
+
+TC:
+- dp 배열을 채우는 데에 마지막 문자가 같을 경우 단순 덧셈, 다를 경우 두 값 비교. 둘 다 O(1).
+- 배열 채우는 것을 m * n회 반복하므로 총 O(m * n).
+"""
+
+
+class Solution:
+    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
+        if len(text2) > len(text1):
+            # 이 최적화까지 해주면 사용하는 메모리 크기가 많이 줄어들 수 있다.
+            text1, text2 = text2, text1
+
+        dp = [[0 for _ in range(len(text2) + 1)] for _ in range(2)]
+
+        for i in range(1, len(text1) + 1):
+            i_prev = (i + 1) % 2
+            i_cur = i % 2
+            for j in range(1, len(text2) + 1):
+                dp[i_cur][j] = (
+                    dp[i_prev][j - 1] + 1
+                    if text1[i - 1] == text2[j - 1]
+                    else max(dp[i_prev][j], dp[i_cur][j - 1])
+                )
+
+        return dp[i_cur][-1]
diff --git a/longest-repeating-character-replacement/haklee.py b/longest-repeating-character-replacement/haklee.py
new file mode 100644
index 000000000..f9e8be415
--- /dev/null
+++ b/longest-repeating-character-replacement/haklee.py
@@ -0,0 +1,36 @@
+"""TC: O(n), SC: O(1)
+
+n은 주어진 문자열의 길이
+
+아이디어:
+- 투 포인터를 써서 문자열의 시작, 끝을 관리하면서 부분 문자열을 만든다.
+- 부분 문자열에 들어있는 문자 중 가장 많은 문자와 k의 합이 문자열의 길이 보다 크면 조건 만족.
+- 부분 문자열에 들어있는 문자 개수를 dict를 써서 관리하자.
+
+SC:
+- 부분 문자열에 들어있는 문자 개수를 관리하는 dict에서 O(1).
+- 부분 문자열의 시작, 끝 인덱스 관리 O(1).
+- 종합하면 O(1).
+
+TC:
+- 부분 문자열의 끝 인덱스를 하나 늘릴 때마다 반환할 값 업데이트. O(1)을 n번 시행하므로 O(n).
+- 시작, 끝 인덱스 수정할 때마다 부분 문자열에 들어있는 문자 개수 업데이트. 시작, 끝 인덱스는
+  많이 수정해봐야 합쳐서 2*n번. 즉, O(1)을 2*n번 시행하므로 O(n).
+- 시작, 끝 인덱스에 1을 더하는 시행. O(1)을 2*n번 시행하므로 O(n).
+- 종합하면 O(n).
+"""
+
+
+class Solution:
+    def characterReplacement(self, string: str, k: int) -> int:
+        char_cnt = {c: 0 for c in set(string)}
+        s = e = 0
+        sol = -1
+        while e < len(string):
+            char_cnt[string[e]] += 1
+            while e - s + 1 > max(char_cnt.values()) + k:
+                char_cnt[string[s]] -= 1
+                s += 1
+            sol = max(e - s + 1, sol)
+            e += 1
+        return sol
diff --git a/merge-two-sorted-lists/haklee.py b/merge-two-sorted-lists/haklee.py
new file mode 100644
index 000000000..42de1fba3
--- /dev/null
+++ b/merge-two-sorted-lists/haklee.py
@@ -0,0 +1,65 @@
+"""TC: O(n), SC: -
+
+n은 주어진 두 리스트의 길이 중 큰 값
+
+아이디어:
+- 주어진 조건에 의해 두 리스트에 들어있는 값들은 non-decreasing이므로, 새로운 리스트를 만들고
+  두 리스트의 앞에 있는 값 중 작은 값을 하나씩 뽑아서 더해주면 된다.
+- 빈 리스트가 주어질 수 있는 것만 유의하자.
+
+SC:
+- 특별히 관리하는 값이 없다.
+
+TC:
+- 모든 노드에 한 번씩 접근해서 리턴할 값에 이어준다. 이어주는 시행마다 O(1).
+- 리턴할 값에 새 노드를 추가할 때마다 값 비교를 한 번씩 한다. O(1).
+- n이 두 리스트 길이 중 큰 값이므로 이어주는 시행은 x는 n <= x <= 2*n 만족.
+- 즉, 총 O(n).
+"""
+
+# Definition for singly-linked list.
+# class ListNode:
+#     def __init__(self, val=0, next=None):
+#         self.val = val
+#         self.next = next
+class Solution:
+    def mergeTwoLists(
+        self, list1: Optional[ListNode], list2: Optional[ListNode]
+    ) -> Optional[ListNode]:
+        # 1. init head
+        # - 두 리스트를 보고 혹시 하나라도 비어있으면 다른 리스트를 리턴한다.
+        # - 둘 다 비어있지 않을 경우 첫 아이템을 보고 둘 중 작은 값을 결과물의 첫 아이템으로 씀.
+        if list1 is None:
+            return list2
+        if list2 is None:
+            return list1
+        # 여기 도달했으면 둘 다 최소한 한 아이템씩은 존재.
+        sol = None
+        if list1.val < list2.val:
+            sol = ListNode(list1.val)
+            list1 = list1.next
+        else:
+            sol = ListNode(list2.val)
+            list2 = list2.next
+
+        sol_head = sol
+
+        # 2. add item
+        # - 앞의 과정을 비슷하게 반복한다.
+        while True:
+            # 언젠가 둘 중 한 리스트는 비게 되므로 무한 루프를 돌지 않는다.
+            if list1 is None:
+                sol_head.next = list2
+                return sol
+            if list2 is None:
+                sol_head.next = list1
+                return sol
+
+            if list1.val < list2.val:
+                sol_head.next = ListNode(list1.val)
+                list1 = list1.next
+            else:
+                sol_head.next = ListNode(list2.val)
+                list2 = list2.next
+
+            sol_head = sol_head.next
diff --git a/sum-of-two-integers/haklee.py b/sum-of-two-integers/haklee.py
new file mode 100644
index 000000000..f7b7dde15
--- /dev/null
+++ b/sum-of-two-integers/haklee.py
@@ -0,0 +1,133 @@
+"""TC: O(1), SC: O(n^2)
+
+-n <= a, b <= n 가정.
+문제에서는 n이 1000으로 주어져있다고 볼 수 있다.
+
+아이디어:
+덧셈을 못 쓰게 한다면 전처리를 통해 모든 a, b 조합에 대한 덧셈 값을 만들어놓고 a, b의 값에 따라서
+필요한 값을 출력하도록 하자. python에서는 음수로 된 인덱스를 지원하므로 이를 활용하자.
+
+- 아래의 코드를 통해서 전처리된 값을 준비한다. 이 코드는 leetcode에서 실행되지 않으므로 더하기를
+  써도 상관 없다.
+```py
+n = 3
+with open("foo.txt", "w") as f:
+    a = [
+        [
+            (i if i <= n else i - 2 * n - 1) + (j if j <= n else j - 2 * n - 1)
+            for j in range(0, 2 * n + 1)
+        ]
+        for i in range(0, 2 * n + 1)
+    ]
+    f.write(str(a))
+```
+
+SC:
+- O(n^2). 정확히는, (2*n+1)^2 개의 정수를 배열에 저장한다.
+
+TC:
+- 인덱스를 통해 바로 접근. O(1).
+"""
+
+
+class Solution:
+    # n = 3일때 예시.
+    def getSum(self, a: int, b: int) -> int:
+        x = [
+            [0, 1, 2, 3, -3, -2, -1],
+            [1, 2, 3, 4, -2, -1, 0],
+            [2, 3, 4, 5, -1, 0, 1],
+            [3, 4, 5, 6, 0, 1, 2],
+            [-3, -2, -1, 0, -6, -5, -4],
+            [-2, -1, 0, 1, -5, -4, -3],
+            [-1, 0, 1, 2, -4, -3, -2],
+        ]
+        return x[a][b]
+
+
+# 단, n = 1000일때 이런 식으로 코드를 짜려고 하면
+# `For performance reasons, the number of characters per line is limited to 10,000.`
+# 하는 문구와 함께 리스트를 복붙할 수가 없다...
+
+
+"""TC: O(n), SC: O(n)
+
+-n <= a, b <= n 가정.
+문제에서는 n이 1000으로 주어져있다고 볼 수 있다.
+
+아이디어:
+전처리 한 것을 가져오는 방법은 못 쓰게 되었지만, 인덱스를 통한 접근은 아직 그대로 쓰고 싶다.
+
+- 문제의 조건을 바꿔서 0 <= a, b <= n라고 해보자. 그리고 n이 3이라고 해보자.
+- a가 0으로 고정되어 있다면, 다음과 같은 배열이 주어졌을때 a+b의 값을 인덱스로 접근할 수 있다.
+    - v = [0, 1, 2, 3] 일때 a + b 값은 v[b]
+- a가 1로 고정되어 있다면,
+    - v = [1, 2, 3, 4] 일때 a + b 값은 v[b]
+- a가 2로 고정되어 있다면,
+    - v = [2, 3, 4, 5] 일때 a + b 값은 v[b]
+- a가 3으로 고정되어 있다면,
+    - v = [3, 4, 5, 6] 일때 a + b 값은 v[b]
+- 위의 배열을 보면 겹치는 숫자들이 많다. 그렇다면 0~6까지 숫자들이 들어있는 배열을 slicing해서
+  쓰면 되지 않을까?
+    - a가 0일때 v = [0, 1, 2, 3, 4, 5, 6] 중
+                    [0, 1, 2, 3] 사용.
+      즉, v[0:4] 사용.
+
+    - a가 1일때 v = [0, 1, 2, 3, 4, 5, 6] 중
+                       [1, 2, 3, 4] 사용.
+      즉, v[1:5] 사용.
+    ...
+    - 일반화하면, v[a:a+n+1] 사용. 이때 a+b 값은 v = list(range(0, 2 * n + 1))일때 v[a:a+n+1][b].
+- 그런데 v[a:a+n+1][b]를 보면 슬라이싱 하는 부분에서 + 기호를 사용했다.
+    - 그렇다면 저기서 더하기 기호를 사용할 필요 없이 슬라이싱의 시작 값과 끝 값도 미리 리스트로 만들고,
+      이 리스트에서 a번째 아이템을 가져오는 방식을 활용해보자.
+    - s = [0, 1, 2, 3], e = [4, 5, 6, 7]일때, v[a:a+n+1][b] = v[s[a]:e[a]][b]가 된다.
+    - 일반화하면, s = list(range(0, n)), e = list(range(n+1, 2*n+1))이면 된다.
+    - e를 만들면서 더하기를 쓴 것처럼 보이지만, 실제로는 n이 주어진 상수이므로 값을 계산해서 넣으면 된다.
+    - 예를 들어, n=3일때 e = list(range(4, 7))이다.
+
+큰 아이디어는 위의 방식으로 설명이 끝났다. 이제 문제는 0 <= a, b <= n이 아니라 -n <= a, b <= n 범위에서도
+위의 방식이 작동하도록 하는 것인데, 먼저 a값은 양수 범위에 두고 b값만 음수로 확장한 상태에서 v를 구해보고,
+그 다음 a도 음수까지 확장하는 식으로 접근하면 된다. 자세한 설명은 생략하고, 둘 다 음수 범위까지 확장한 뒤
+실제로 작동하는 결과물을 설명하는 것으로 대신하겠다.
+
+- n은 3이라고 가정하겠다.
+- v = [0, 1, 2, 3, 4, 5, 6, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, -6, -5, -4, -3, -2, -1]
+    - list(range(0, n+1))에 list(range(-n, 0))을 이어붙인 것을 두 번 반복했다.
+    - 두 번 반복한 이유는 a값이 음수가 된 상황에 대응하기 위함이다. 아래의 설명을 이어서 보도록 하자.
+- s = list(range(0, 4 * n + 1)) = list(range(0, 13))이다.
+    - 이렇게 하면 a가 음수가 되었을때도 slicing을 시작할 인덱스는 양수로 유지할 수 있다.
+    - b를 0으로 고정하면 slicing을 시작하는 인덱스에 있는 아이템이 a+b의 값이 되어야 한다.
+    - 이를 위의 v와 같이 생각하면 v의 앞 13개 아이템을 취한 [0, 1, 2, 3, 4, 5, 6, -6, -5, -4, -3, -2, -1]
+      배열이 있을때 a가 취할 수 있는 값의 범위는 -3~3이므로 [0, 1, 2, 3, ..., -3, -2, -1] 중 하나부터
+      slicing이 시작된다고 보면 된다.
+    - 그러니까 쉽게 말해서 a의 범위로 인해 slicing이 아이템 4부터 시작해서 [4, 5, 6, -6, ...] 하는 일이
+      일어나지는 않는다는 뜻.      
+- slicing한 배열의 크기는 4*n+1이어야 한다. e는 s의 각 아이템에 4*n+1을 더한 값이면 된다.
+    - 4*n+1은 관찰을 통해 얻을 수 있는 값이다.a, b의 합의 최소가 -2*n, 최대가 2*n이어서 그 사이에 있는
+      숫자들이 총 4*n+1개 있다는 것에서 비롯된 숫자다.
+        - 끝에 예시를 보면 이해가 좀 더 편하다.
+    - 정리하면, e = list(range(4*n+1, 8*n+2)) = list(range(13, 26))이다.
+- a+b 값은 v[s[a] : e[a]][b] 로 구할 수 있다.
+- 예를 들어, a=2, b=-3이라고 할때
+    - v[s[a] : e[a]] = v[2:15] = [2, 3, 4, 5, 6, -6, -5, -4, -3, -2, -1, 0, 1]다.
+    - b가 -3이므로 위의 slicing된 배열에서 뒤에서 세 번째 아이템을 찾으면 된다. 즉, -1이다.
+    - 잘 관찰하면 덧셈의 결과가 될 수 있는 값은 [2, 3, 4, 5, ..., -1, 0, 1] 밖에 없다. 사이에 있는 숫자는
+      b의 범위가 제한되어 있어서 접근 불가능한, 즉, 필요 없는 숫자들이라고 보면 된다.
+
+SC:
+- 코드 참조. O(n).
+
+TC:
+- 인덱스를 통해 바로 접근. O(1).
+"""
+
+
+class Solution:
+    def getSum(self, a: int, b: int) -> int:
+        x = list(range(0, 2001))
+        x.extend(list(range(-2000, 0)))
+        v = x * 2  # SC: O(n)
+        s = list(range(0, 4001))  # SC: O(n)
+        e = list(range(4001, 8002))  # SC: O(n)
+        return v[s[a] : e[a]][b]