diff --git a/longest-substring-without-repeating-characters/haklee.py b/longest-substring-without-repeating-characters/haklee.py new file mode 100644 index 00000000..b3b8cfc9 --- /dev/null +++ b/longest-substring-without-repeating-characters/haklee.py @@ -0,0 +1,51 @@ +"""TC: O(n), SC: O(n) + +아이디어: +투포인터로 문자열의 시작, 끝 인덱스를 관리한다. 그리고 문자열 안에 있는 문자를 set으로 관리한다. +- 시작~끝 사이에 모든 문자가 서로 다르면 끝 인덱스를 하나 뒤로 옮긴다. +- 새로운 문자열의 제일 끝에 있는 문자가 혹시 set 안에 있으면 시작 인덱스를 뒤로 옮기는 작업을 + 해당 문자랑 같은 문자가 나올 때까지 진행한다. 즉, 끝을 고정하고 앞을 계속 옮겨서 새로운 문자열에 + 있는 모든 문자들이 다른 문자가 되도록 만든다. + - e.g.) abcdefd -> abcdefd + ^ ^ ^ ^ + s e s e +- 위의 과정을 계속 반복하면서 문자열의 최대 길이 값을 갱신한다. + +SC: +- 새로운 문자열의 시작과 끝 인덱스 값을 관리하는 데에 O(1). +- 새로운 문자열 안에 들어있는 문자를 set으로 관리하는 데에 최악의 경우 n개의 문자가 모두 다를때 O(n). +- 최대 문자열 길이를 관리하는 데에 O(1). +- 종합하면 O(n). + +TC: +- s, e는 모두 문자열의 인덱스를 나타내므로 s, e값을 아무리 많이 업데이트 해도 각가 문자열 길이보다 + 많이 업데이트 할 수는 없다. 즉, O(n). +- set에서 특정 문자가 들어있는지 체크하고 set에 문자를 더하거나 제거하는 데에 O(1). 이 작업을 아무리 + 많이 해도 s, e를 업데이트 하는 회수 만큼 진행하므로 총 O(n). +- 최대 문자열 길이를 업데이트 하는 데에 O(1). 이 또한 아무리 많이 진행해도 O(n). +- 종합하면 O(n). +""" + + +class Solution: + def lengthOfLongestSubstring(self, string: str) -> int: + if len(string) < 1: + return 0 + s = e = 0 + letter_set = set([string[0]]) + sol = 1 + while True: + e += 1 + if e == len(string): + break + + if string[e] in letter_set: + while True: + letter_set.discard(string[s]) + if string[s] == string[e]: + break + s += 1 + s += 1 + letter_set.add(string[e]) + sol = max(len(letter_set), sol) + return sol diff --git a/number-of-islands/haklee.py b/number-of-islands/haklee.py new file mode 100644 index 00000000..3ad8e4d3 --- /dev/null +++ b/number-of-islands/haklee.py @@ -0,0 +1,41 @@ +"""TC: O(m * n), SC: O(m * n) + +아이디어: +- 특정 칸이 1일 경우 이와 같은 섬 안에 있는 모든 칸들을 0으로 바꿔주는 `remove_ground` 함수 구현. +- grid 내의 모든 칸들을 돌면서 `remove_ground`가 몇 번 최초 호출(즉, 재귀 호출 아님) 되었는지 + 세면 전체 섬이 몇 개 있는지 찾을 수 있다. + +SC: +- 모든 칸이 전부 1일 경우 remove_ground의 호출 스택 깊이가 m*n이 된다. 즉, O(m * n). + +TC: +- 각 노드는 최대 5번씩 접근될 수 있다. + - 이웃한 칸에서 remove_ground 하면서 접근 + - 최외곽에서 해당 칸이 1인지 체크하면서 접근 +- 접근하고 나서 하는 연산이 O(1). + - 해당 칸의 값이 1인지 체크하는 데에 O(1). + - check_inside 연산을 4번 할 수 있는데 각각 O(1). + - 종합하면 O(1). +- 종합하면 O(m * n). +""" + + +class Solution: + def numIslands(self, grid: List[List[str]]) -> int: + minx, miny, maxx, maxy = 0, 0, len(grid[0]) - 1, len(grid) - 1 + + def remove_ground(i, j): + if minx <= j <= maxx and miny <= i <= maxy and grid[i][j] == "1": + grid[i][j] = "0" + remove_ground(i - 1, j) + remove_ground(i + 1, j) + remove_ground(i, j - 1) + remove_ground(i, j + 1) + + sol = 0 + for i in range(maxy + 1): + for j in range(maxx + 1): + if grid[i][j] == "1": + sol += 1 + remove_ground(i, j) + return sol diff --git a/reverse-linked-list/haklee.py b/reverse-linked-list/haklee.py new file mode 100644 index 00000000..d0603f8a --- /dev/null +++ b/reverse-linked-list/haklee.py @@ -0,0 +1,28 @@ +"""TC: O(n), SC: O(1) + +아이디어: +첫 아이템부터 차례대로 next로 넘어가면서 직전에 보았던 노드를 next에 넣어준다. + +SC: +- 직전 노드를 관리한다. O(1). + +TC: +- 모든 노드에 대해 직전 노드를 next에 대입한다. +- 그 외에 다른 연산도 O(1). 코드 참조. +- 종합하면 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 reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + prev, cur = None, head + while cur: + next = cur.next + cur.next = prev + prev, cur = cur, next + return prev diff --git a/set-matrix-zeroes/haklee.py b/set-matrix-zeroes/haklee.py new file mode 100644 index 00000000..129c2a68 --- /dev/null +++ b/set-matrix-zeroes/haklee.py @@ -0,0 +1,38 @@ +"""TC: O(m * n), SC: O(m + n) + +아이디어: +모든 칸을 훑으면서 어떤 row, column을 0으로 바꿔줘야 하는지 찾은 다음 0으로 바꾸는 시행을 한다. + +SC: +- 바꿔야 하는 column, row를 set으로 관리. 각각 O(m), O(n)이므로 종합하면 O(m + n). + +TC: +- 모든 칸을 돌면서 어떤 column과 row를 0으로 바꿔야 하는지 체크한다. O(m * n). +- 0으로 바꿔야 하는 모든 column을 0으로 바꾼다. 모든 column을 다 0으로 바꿔야 할 경우 모든 칸에 + 접근해서 0을 넣어주어야 하므로 O(m * n). +- row도 column과 똑같이 접근 가능하다. O(m * n). +- 종합하면 O(m * n). +""" + + +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + """ + Do not return anything, modify matrix in-place instead. + """ + m, n = len(matrix), len(matrix[0]) + col_to_change, row_to_change = set(), set() + + for i in range(m): + for j in range(n): + if matrix[i][j] == 0: + col_to_change.add(i) + row_to_change.add(j) + + for i in col_to_change: + for j in range(n): + matrix[i][j] = 0 + + for j in row_to_change: + for i in range(m): + matrix[i][j] = 0 diff --git a/unique-paths/haklee.py b/unique-paths/haklee.py new file mode 100644 index 00000000..92c22bc7 --- /dev/null +++ b/unique-paths/haklee.py @@ -0,0 +1,31 @@ +"""TC: O(m + n), SC: O(1) + +아이디어: +오른쪽으로 가는 회수 m-1, 아래로 가는 회수 n-1을 섞을 수 있는 방법의 수를 구하면 된다. +즉, m+n-2회의 이동 중 m-1개를 뽑아서 오른쪽으로 가고, 나머지를 아래로 가면 된다. +즉, (m+n-2)C(m-1)을 계산하면 된다. + +큰 수 연산이 가능한 언어를 사용하면 nCk = n! / (k! * (n - k)!) 값을 계산하면 된다. +파이썬의 math.comb 함수는 아래와 같이 작동한다. +(ref: https://docs.python.org/ko/3/library/math.html#math.comb) +- math.comb(n, k)을 계산하면 k <= n이면 n! / (k! * (n - k)!)로 평가되고, k > n이면 0으로 평가됩니다. +이 함수를 써서 나온 결과값을 그대로 반환하자. + +SC: +- 곱셈, 나눗셈 연산 결과 값 관리. O(1). +- 아주 큰 숫자를 다룰 경우 이렇게 보면 안 될 수도 있지만, 문제 조건을 보아하니 int64 범위 내에서 + 곱셈과 나눗셈 연산 값들이 모두 처리되는 것으로 보인다. 즉, SC가 그리 중요하지는 않다. + +TC: +- (m+n-2)C(m-1) = (m+n-2)! / (m-1)! * (n-1)! +- 각 곱셈 값을 구하는 데에 O(m + n), O(m), O(n). 셋 다 더하면 O(m + n). +- 나눗셈에 O(1). +- 종합하면 O(m + n) +""" + +from math import comb + + +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + return comb(m + n - 2, n - 1)