diff --git a/longest-consecutive-sequence/haklee.py b/longest-consecutive-sequence/haklee.py new file mode 100644 index 00000000..d3a5cae2 --- /dev/null +++ b/longest-consecutive-sequence/haklee.py @@ -0,0 +1,36 @@ +"""TC: O(n), SC: O(n) + +아이디어: +- nums 안에는 여러 consecutive sequence(이하 cs)가 존재할 것이다(길이 1인 것까지 포함). +- 이 cs는 모두 제일 앞 숫자를 가지고 있다. +- 제일 앞 숫자는 그 숫자 바로 앞에 숫자가 없다는 특징을 가지고 있다. + - 즉, i가 제일 앞 숫자라면 i-1은 nums 안에 없다. +- nums에서 제일 앞 숫자를 찾은 다음, 이 숫자부터 뒤로 이어지는 cs를 찾아서 길이를 구할 수 있다. +- cs의 길이 중 제일 긴 것을 찾아서 리턴하면 된다. + +SC: +- set(nums)에서 O(n). +- 위 set에서 모든 아이템을 돌면서 i-1이 set 안에 포함되지 않는 i를 찾아서 리스트로 만들때 O(n). +- 총 O(n). + +TC: +- set(nums)에서 O(n). +- 위 set에서 모든 아이템을 돌면서 i-1이 set 안에 포함되지 않는 i를 찾는 데에 O(n). +- 각 cs의 제일 앞 숫자부터 이어지는 숫자들이 set 안에 있는지 체크하는 데에 총 O(n). +- 총 O(n). +""" + + +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + s = set(nums) # TC:O(n), SC:O(n) + seq_start_candidate = [i for i in s if i - 1 not in s] # TC:O(n), SC:O(n) + sol = 0 + + # 아래의 for문 내에서는 s에 속한 아이템에 한 번씩 접근한다. TC:O(n) + for i in seq_start_candidate: + seq_len = 1 + while i + seq_len in s: + seq_len += 1 + sol = max(seq_len, sol) + return sol diff --git a/maximum-product-subarray/haklee.py b/maximum-product-subarray/haklee.py new file mode 100644 index 00000000..8cc86695 --- /dev/null +++ b/maximum-product-subarray/haklee.py @@ -0,0 +1,65 @@ +"""TC: O(n), SC: O(1) + +※ 코드를 보고 대충 뭘 했는지 감을 잡은 다음에 아래의 아이디어에서 p, n 구간이 나오는 부분만 보면 + 좀 더 빠른 이해가 가능하다. + +아이디어: +- 곱에 0이 섞이면 아무리 많은 수를 곱해도 결과는 0이다. +- 0이 등장하지 않는 어떤 subarray가 주어졌다고 하자. + - 여기에 음수가 짝수 번 등장하면 모든 숫자를 곱한 것이 가장 큰 곱이 된다. + - 음수가 홀수 번, 총 r번 등장한다고 해보자. 이 배열을 다음과 같이 구조화할 수 있다. + - 양수를 p, 음수를 n이라고 표현하자. + - 이 배열은 [p, ..., p, n, p, ... p, n, ..., n, p, ..., p] 꼴로 표현이 가능하다. + 이때, n은 총 r번 등장하고, n은 p가 여러 번(0번도 가능) 등장하는 묶음 사이에 존재한다. + - p가 여러 번(0번도 가능) 등장하는 것을 P라고 묶어서 표현하면 아래와 같이 볼 수 있다. + - [(P), n, (P), n, ..., n, (P), n, (P)] + - n을 짝수 번 곱해야 양수가 나온다. 연속된 값을 최대한 많이 곱하려고 하므로, 한쪽 끝에 + 등장하는 n을 뺀 나머지 n들을 곱하는 것이 가장 좋은 전략이다. + - 위의 전략에 따라 배열의 숫자를 곱하려고 하면 다음 둘 중 하나가 가장 큰 숫자다. + - [(P), n, (P), n, ..., n, (P), n, (P)] + └───────────────────────┘ + 이 구간의 숫자들을 모두 곱함 + - [(P), n, (P), n, ..., n, (P), n, (P)] + └───────────────────────┘ + 이 구간의 숫자들을 모두 곱함 + - 즉, 앞에서부터 숫자를 계속 곱하면서 max값을 찾은 것, 혹은 뒤에서부터 숫자를 계속 + 곱하면서 max값을 찾은 것, 둘 중 하나가 위의 구간에서의 최대 곱셈 값이 된다. +- 우리에게 주어진 전체 array는 다음과 같이 표현 가능하다. + - 0이 등장하지 않는 길이 1 이상의 subarray를 (S), 0으로만 이루어진 길이 0 이상의 subarray를 + (0)이라고 하면 아래와 같이 표현이 가능하다. + - [(0), (S), (0), (S), ..., (S), (0), (S), (0)] + - 전체 array에서 최대 subarray 곱은 0이거나, 혹은 위의 각 S에서의 최대 곱들 중 최대 값이다. +- 위의 아이디어를 활용하면 다음의 방식으로 최대 subarray의 곱을 찾을 수 있다. + - nums의 앞에서부터 숫자를 하나씩 곱해가면서 최대 곱을 찾음. 단, 중간에 0이 나와서 최대 곱 + 값이 0이 되었을 경우 이를 다시 1로 바꿔줘서 위의 아이디어를 적용할 수 있도록 세팅. + - 똑같은 작업을 nums의 뒤에서부터 숫자를 하나씩 곱해가면서 진행함. + + +SC: +- solution을 저장하는 데에 O(1). +- 곱셈 값을 저장하는 데에 O(1). +- 총 O(1). + +TC: +- 리스트를 앞에서부터 순회하면서 곱/max 연산. O(n). +- 리스트를 뒤에서부터 순회하면서 곱/max 연산. O(n). +- 총 O(n). +""" + + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + sol = nums[0] + p = 1 + for i in nums: + if p == 0: + p = 1 + p *= i + sol = max(p, sol) + p = 1 + for i in reversed(nums): + if p == 0: + p = 1 + p *= i + sol = max(p, sol) + return sol diff --git a/missing-number/haklee.py b/missing-number/haklee.py new file mode 100644 index 00000000..c256022d --- /dev/null +++ b/missing-number/haklee.py @@ -0,0 +1,26 @@ +"""TC: O(n), SC: O(n) + +아이디어: +- [0, ..., n]에서 한 숫자만 빠져있는 상황. +- [0, ..., n]을 set으로 만든 다음 특정 숫자가 이 set에 있는지 체크하면 된다. +- 그런데 그렇게 구현하나 위 set에서 set(nums)를 빼고 남은 숫자를 취하나 최악의 경우 + 같은 성능이 나올테니 더 코드가 짧아지도록 후자의 방식으로 구현해보자. + + +SC: +- [0, ..., n]으로 set을 만드는 데에 O(n). +- set(nums)에서 O(n). +- 총 O(n). + +TC: +- [0, ..., n]으로 set을 만드는 데에 O(n). +- set(nums)에서 O(n). +- set에 difference(아래 코드에서는 `-`)를 하는 데에 O(n). +- set에 pop을 하는 데에 O(1). +- 총 O(n). +""" + + +class Solution: + def missingNumber(self, nums: List[int]) -> int: + return (set(range(len(nums) + 1)) - set(nums)).pop() diff --git a/valid-palindrome/haklee.py b/valid-palindrome/haklee.py new file mode 100644 index 00000000..613b4536 --- /dev/null +++ b/valid-palindrome/haklee.py @@ -0,0 +1,24 @@ +"""TC: O(n), SC: O(n) + +아이디어: +문자열을 보고 숫자 혹은 알파벳인 문자만 뽑아서 새 문자열을 만들고, 대문자는 소문자로 바꾼다. +구현이 어렵지는 않지만 귀찮을 수 있는데, python에는 위 과정을 `isalnum()`, `lower()` 함수로 +쉽게 처리할 수 있다. 마지막으로 새로 만든 문자열을 뒤집어서 원래 문자열과 같은지 확인하면 된다. + + +SC: +- 문자열을 필요한 문자만 남기는 과정에서 O(n). +- 문자열을 뒤집어서 저장. O(n). +- 즉, O(n). + +TC: +- 문자열을 필요한 문자만 남기는 과정에서 O(n). +- 문자열 뒤집기. O(n). +- 새로 만든 문자열과 뒤집은 문자열 palindrome 체크. O(n). +- 즉, O(n). +""" + + +class Solution: + def isPalindrome(self, s: str) -> bool: + return (l := [c.lower() for c in s if c.isalnum()]) == l[::-1] diff --git a/word-search/haklee.py b/word-search/haklee.py new file mode 100644 index 00000000..c9b0310f --- /dev/null +++ b/word-search/haklee.py @@ -0,0 +1,61 @@ +"""TC: O(m * n * (4^l)), SC: O(m * n) + +아이디어: +- 격자판의 각 칸을 노드로, 이웃한 칸들의 관계를 엣지로 생각하면 격자판을 그래프로 볼 수 있다. +- 위 그래프에서 dfs를 돌린다. +- 이때, 기존에 방문했던 노드를 재방문하지 않도록 visited라는 2차원 배열을 같이 관리해주자. + +SC: +- visited 배열에서 O(m * n) +- 호출 스택은 찾고자 하는 단어의 길이 l, 즉, O(l). + - 그런데 l이 격자 전체 칸 개수보다 클 수 없으므로 무시 가능. +- 총 O(m * n). + +TC: +- visited 배열 세팅, O(m * n) +- dfs, 최악의 경우 + - 단어를 찾는 시도를 하는 데에 4^l 만큼의 탐색이 걸림 + - 그런데 답을 찾을 수 있는 시작 칸을 하필 제일 마지막으로 탐색한 경우 위의 시도를 m*n번 해야함 + - 즉, O(m * n * (4^l)) +- 총 O(m * n * (4^l)) +""" + + +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + r, c = len(board), len(board[0]) + visited = [[False for _ in range(c)] for _ in range(r)] + + def search(ind: int, pos: tuple[int, int]) -> bool: + if ind == len(word): + # 찾는 데에 성공. + return True + + if not (0 <= pos[0] < r and 0 <= pos[1] < c): + # 격자판을 벗어남. + return False + + if visited[pos[0]][pos[1]]: + # 이미 방문함. + return False + + if word[ind] != board[pos[0]][pos[1]]: + # 글자가 안 맞음. + return False + + visited[pos[0]][pos[1]] = True # 방문한 것으로 체크 + + found = ( + search(ind + 1, (pos[0] - 1, pos[1])) # 상 + or search(ind + 1, (pos[0] + 1, pos[1])) # 하 + or search(ind + 1, (pos[0], pos[1] - 1)) # 좌 + or search(ind + 1, (pos[0], pos[1] + 1)) # 우 + ) # 다음 글자 찾기 + + # 앞에서 못 찾았을 경우에는 방문을 해제해야 한다. + # 찾은 경우에는 방문을 해제하든 말든 상관 없음. + visited[pos[0]][pos[1]] = False + + return found + + return any(search(0, (i, j)) for i in range(r) for j in range(c))