From 5e09250efe695a11d220ee465eb2ec1ab43dc601 Mon Sep 17 00:00:00 2001 From: Hak Lee Date: Thu, 19 Sep 2024 19:50:17 +0900 Subject: [PATCH] solutions --- container-with-most-water/haklee.py | 105 ++++++++++++++++++ .../haklee.py | 59 ++++++++++ spiral-matrix/haklee.py | 74 ++++++++++++ valid-parentheses/haklee.py | 45 ++++++++ 4 files changed, 283 insertions(+) create mode 100644 container-with-most-water/haklee.py create mode 100644 design-add-and-search-words-data-structure/haklee.py create mode 100644 spiral-matrix/haklee.py create mode 100644 valid-parentheses/haklee.py diff --git a/container-with-most-water/haklee.py b/container-with-most-water/haklee.py new file mode 100644 index 000000000..0e330664b --- /dev/null +++ b/container-with-most-water/haklee.py @@ -0,0 +1,105 @@ +"""TC: O(n), SC: O(1) + +아이디어: +높이 리스트 l이 주어졌다고 하자. 그리고 이 안에 있는 최대 수조 면적을 f(l)이라고 하자. + + □ □ □ + □ □ □ □ + □ □ □ □ □ □ □ □ □ +□ □ □ □ □ □ □ □ □ □ □ □ +□ □ □ □ □ □ □ □ □ □ □ □ □ □ +2,5,3,3,1,3,1,2,2,5,3,5,3,4 + ^ + l + + +그러면 다음이 항상 성립한다. + +- l의 양 끝의 값 중 작은 값 x가 앞쪽에 있었다고 해보자. x를 뺀 리스트 l`을 만든다. + - 이때 작은 값이 뒷쪽에 있었어도 일반성을 잃지 않는다. + + │□ □ □ + │□ □ □ □ + │□ □ □ □ □ □ □ □ □ +□│□ □ □ □ □ □ □ □ □ □ □ +□│□ □ □ □ □ □ □ □ □ □ □ □ □ +2│5,3,3,1,3,1,2,2,5,3,5,3,4 +^ ^ +x l` + + +- f(l)은 그렇다면 + - f(l`)이거나(즉, x를 쓰지 않았거나) + - x를 써서 만든 수조 면적 중에 있다. + +- 그런데 x는 l의 양 끝 값 중에 작은 값이므로, 아래와 같은 분석을 할 수 있다. + 1) x를 써서 만드는 수조의 높이는 아무리 높아도 x다. x보다 작아질 수는 있어도, x보다 커질 수는 없다. + 2) x를 써서 만드는 수조의 폭은 l의 다른쪽 끝에 있는 높이를 선택했을때 최대가 된다. +- 그러므로, x를 써서 만들 수 있는 수조의 최대 크기는 l의 다른쪽 끝에 있는 높이를 선택한 경우 나온다. + +위의 내용을 아래의 설명을 통해 시각적으로 확인할 수 있다. + +- 양 끝을 선택한 경우 x로 만들 수 있는 최대 면적이다. + + │□ □ □ + │□ □ □ □ + │□ □ □ □ □ □ □ □ □ +■│■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ +■│■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ +2│5,3,3,1,3,1,2,2,5,3,5,3,4 +^ ^ + +- x는 그대로 둔 채 다른쪽 끝을 안쪽으로 더 이동하면 수조 높이는 동일한데 폭은 더 작아진다. + + │□ □ □ + │□ □ □ □ + │□ □ □ □ □ □ □ □ □ +■│■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ □ □ +■│■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ □ □ +2│5,3,3,1,3,1,2,2,5,3,5,3,4 +^ ^ + +- 심지어 x보다 작은 높이 값을 선택한 경우 수조 높이도 작아지고 폭도 작아지는 일이 일어난다. + + │□ □ □ + │□ □ □ □ + │□ □ □ □ □ □ □ □ □ +□│□ □ □ □ □ □ □ □ □ □ □ +■│■ ■ ■ ■ ■ ■ □ □ □ □ □ □ □ +2│5,3,3,1,3,1,2,2,5,3,5,3,4 + + +즉, 위의 내용을 종합하면 다음을 확인할 수 있다. +- f(l) = max( (l의 양 끝 높이를 선택해서 만든 수조 넓이), f(l`) ) +- 그런데 f(l`)도 f(l)을 구한 것과 같은 방식으로 구할 수 있다. 즉, 새로 만들어진 l`의 양 끝 높이 중 + 짧은 쪽을 뺀 리스트 l``을 만들어서 위의 과정을 반복할 수 있다. +- 즉, f(l)은 아래의 과정을 반복하여 구할 수 있다. + - l의 양 끝 높이를 써서 수조 넓이를 구하고, 기존 최대 넓이와 비교하여 더 큰 값을 최대 넓이에 대입한다. + - l에서 짧은 쪽 높이를 뺀다. + - 위 과정을 l에 아이템이 하나만 남을 때까지 반복. + + +SC: +- 투 포인터를 써서 l의 시작, 끝 인덱스를 관리하면 O(1). +- 수조 최대 넓이값 관리, O(1). +- 종합하면 O(1). + +TC: +- 리스트의 양 끝 높이를 통해 면적 구하기, O(1). +- 포인터 이동 O(1). +- 포인터 이동시 두 포인터 사이의 거리가 1씩 항상 감소하므로 위 과정을 최대 n-2번 반복. +- 종합하면 O(n). +""" + + +class Solution: + def maxArea(self, height: List[int]) -> int: + max_area = -1 + s, e = 0, len(height) - 1 + while s < e: + max_area = max(max_area, (e - s) * min(height[s], height[e])) + if height[s] > height[e]: + e -= 1 + else: + s += 1 + return max_area diff --git a/design-add-and-search-words-data-structure/haklee.py b/design-add-and-search-words-data-structure/haklee.py new file mode 100644 index 000000000..d1cb91ce1 --- /dev/null +++ b/design-add-and-search-words-data-structure/haklee.py @@ -0,0 +1,59 @@ +"""search시: TC: O(n), SC: O(n) + +word의 max length가 w, `addWord`를 통해 만들어진 노드 개수가 n개라고 하자. + +아이디어: +`implement-trie-prefix-tree` 문제에서 구현한 trie에서 search 부분만 recursive한 dfs 구현으로 수정. + +SC: +- trie의 노드 개수 O(n). +- dfs시 스택 깊이는 최대 w. 즉, O(w). 그런데 스택 깊이는 노드 개수보다 클 수 없다. 즉, O(w) < O(n). +- 종합하면, O(n) + O(w) < O(n) + O(n) = O(n). + +TC: +- 최악의 경우 모든 노드 순회. O(n). + +""" + + +class WordDictionary: + + def __init__(self): + self.next: dict[str, WordDictionary] = {} + self.end: bool = False + + def addWord(self, word: str) -> None: + cur = self + + for c in word: + cur.next[c] = cur.next.get(c, WordDictionary()) + cur = cur.next[c] + + cur.end = True + + def search(self, word: str) -> bool: + cur = self + + return self._search(cur, word, 0) + + def _search(self, trie, word: str, ind: int) -> bool: + if ind == len(word): + return trie.end + + c = word[ind] + + if c == ".": + return any( + [self._search(node, word, ind + 1) for node in trie.next.values()] + ) + + if c not in trie.next: + return False + + return self._search(trie.next[c], word, ind + 1) + + +# Your WordDictionary object will be instantiated and called as such: +# obj = WordDictionary() +# obj.addWord(word) +# param_2 = obj.search(word) diff --git a/spiral-matrix/haklee.py b/spiral-matrix/haklee.py new file mode 100644 index 000000000..fc970a6cd --- /dev/null +++ b/spiral-matrix/haklee.py @@ -0,0 +1,74 @@ +"""search시: TC: O(m*n), SC: O(1) + +아이디어: +2차원 배열에서 아직 훑고 가지 않은 영역을 →, ↓, ←, ↑ 방향으로 훑는다. 이때, 한 번에 훑고 갈 선의 +시작과 끝 역할을 할 값들(즉, boundary 값들)을 잘 관리해준다. +- top, bottom, left, right의 역할을 할 값을 각각 0, m, 0, n으로 초기화. +- → 방향으로 훑고 나서 top을 한 칸 아래로(즉, top++). +- ↓ 방향으로 훑고 나서 right를 한 칸 왼쪽으로(즉, right--). +- ← 방향으로 훑고 나서 bottom을 한 칸 위로(즉, bottom--). +- ↑ 방향으로 훑고 나서 left를 한 칸 오른쪽으로(즉, left++). +- right가 left보다 왼쪽에 있거나 top이 bottom보다 아래 있으면 탐색 종료. + +SC: +- boundary 값만 관리. O(1). + +TC: +- 배열의 모든 아이템에 정확히 한 번씩 접근. O(m*n). +- 선을 한 번 긋는 과정에서 일어나는 일이 총 O(1)인데(코드 참조), 이게 아무리 많아도 O(m*n)을 넘지는 + 못한다. 한 선에 최소 하나의 값은 들어있기 때문. +- 종합하면 O(m*n). + +""" + + +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + def read_line(start: tuple[int, int], end: tuple[int, int]) -> list[int]: + if start[0] == end[0]: + # column 방향 + s, e = sorted([start[1], end[1]]) # TC: O(1) + line = range(s, e + 1) + if start[1] > end[1]: # TC: O(1) + line = reversed(line) + return [matrix[i][start[0]] for i in line] + else: + # row 방향 + s, e = sorted([start[0], end[0]]) # TC: O(1) + line = range(s, e + 1) + if start[0] > end[0]: # TC: O(1) + line = reversed(line) + return [matrix[start[1]][i] for i in line] + + top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 + sol = [] + while True: + # 위 + if right < left: + break + line = read_line((left, top), (right, top)) + sol += line + top += 1 # TC: O(1) + + # 오른쪽 + if top > bottom: + break + line = read_line((right, top), (right, bottom)) + sol += line + right -= 1 # TC: O(1) + + # 아래 + if right < left: + break + line = read_line((right, bottom), (left, bottom)) + sol += line + bottom -= 1 # TC: O(1) + + # 왼쪽 + if top > bottom: + break + line = read_line((left, bottom), (left, top)) + sol += line + left += 1 # TC: O(1) + + return sol diff --git a/valid-parentheses/haklee.py b/valid-parentheses/haklee.py new file mode 100644 index 000000000..a30e7bef6 --- /dev/null +++ b/valid-parentheses/haklee.py @@ -0,0 +1,45 @@ +"""TC: O(n), SC: O(n) + +문자열 길이 n + +아이디어: +- 문자열을 앞에서부터 훑으면서 + - 괄호 시작을 발견하면 스택에 넣는다. + - 괄호 끝을 발견하면 스택의 제일 마지막에 있는 아이템을 pop해서 매칭이 되는지 확인한다. +- 이때 다음과 같은 예외 상황들을 조심한다. + - 스택에 아이템이 하나도 없는데 pop을 하려는 상황 방지. + - e.g.) "))))" + - 모든 문자열을 훑고 지나갔는데 스택에 아이템이 남아있는 경우도 실패다. + - e.g.) "((((" + +SC: +- 시작 괄호 리스트 O(1). +- 끝 괄호와 매칭되는 시작 괄호 dict O(1). +- 시작 괄호만 있는 경우 스택에 쌓이는 아이템은 총 n개다. O(n). +- 종합하면 O(n). + +TC: +- 문자 총 n개에 대해 아래를 반복한다. + - 시작 괄호인지 체크. O(1). + - 시작 괄호인 경우 스택에 쌓기 O(1). + - 끝 괄호인 경우 스택에서 pop할때 O(1), 괄호 매칭 체크시 O(1). +- 종합하면 O(n). +""" + + +class Solution: + def isValid(self, s: str) -> bool: + stack = [] + openings = ["(", "{", "["] + ending_match = { + ")": "(", + "}": "{", + "]": "[", + } + for c in s: + if c in openings: + stack.append(c) + else: + if not stack or ending_match[c] != stack.pop(): + return False + return False if stack else True