diff --git a/linked-list-cycle/liza0525.py b/linked-list-cycle/liza0525.py new file mode 100644 index 0000000000..e85adb5da5 --- /dev/null +++ b/linked-list-cycle/liza0525.py @@ -0,0 +1,48 @@ +# 7기 풀이 +class Solution: + # 1. 첫번째 풀이: 이 풀이는 한 칸 씩 헤드를 옮기는 것과 두 칸 씩 헤드를 옮기는 것을 비교하여 + # 헤드가 동일해지면 해당 리스트는 순환한다는 아이디어에서 착안 + # 시간 복잡도: O(n) + # - 리스트의 길이(n) 만큼의 시간이 최대 + # 공간 복잡도: O(1) + # - slow, fast 변수만 사용 + def hasCycle(self, head: Optional[ListNode]) -> bool: + slow = head + fast = head + + while slow and fast: + slow = slow.next + + if not fast.next: + # fast의 다음 노드가 없다면 + # 순환이 되지 않다는 것을 의미하므로 loop 탈출 + break + fast = fast.next.next + + if slow == fast: + # 같아지는 순간이 순환한다는 것을 의미하므로 True로 early return + return True + + # loop 탈출 조건에 의해 순환하지 않음을 확인하여 False return + return False + + # 2. 두번째 풀이: 이미 방문한 노드에 다시 방문하는 경우에 순환한다고 판단 + # 시간 복잡도: O(n) + # - 리스트의 길이(n) 만큼의 시간이 최대 + # 공간 복잡도: O(n) + # - list의 길이(n)이 최대 공간 복잡도(checked가 늘어남) + def hasCycle(self, head: Optional[ListNode]) -> bool: + checked = set() # 노드 방문 여부를 저장 + curr = head + + while curr: # curr가 None이 되기 전까지(== 끝에 도달할 때까지) + if curr in checked: + # 이미 방문한 노드라면 순환한다는 의미이므로 True로 early return + return True + + # 방문한 노드는 checked에 추가 + checked.add(curr) + curr = curr.next # 다음 node 탐색 + + # loop 탈출 조건에 의해 순환하지 않음을 확인하여 False return + return False diff --git a/maximum-product-subarray/liza0525.py b/maximum-product-subarray/liza0525.py new file mode 100644 index 0000000000..648bdaf9f6 --- /dev/null +++ b/maximum-product-subarray/liza0525.py @@ -0,0 +1,27 @@ +# 7기 풀이 +# 시간 복잡도: O(n) +# - nums의 길이(n)만큼의 시간 복잡도 +# 공간 복잡도: O(1) +# - 몇 개의 변수만 사용 +class Solution: + # 문제에서는 이어지는 subarray 내에 음수가 짝수 번 만큼 있다면 최대가 만들어지기도 하므로 + # max_val과 min_val을 가지고 음수의 곱도 대응할 수 있도록 한다. + def maxProduct(self, nums: List[int]) -> int: + max_val, min_val = nums[0], nums[0] + res = nums[0] + + for num in nums[1:]: + prev_max_val = max_val # 이전 max_val은 업데이트 되기 전에 잠시 저장 + + # 현재 loop에서 num과 max_val(이전까지의 최대곱) * num과 min_val(이전까지의 최소곱) * num 중 + # 가장 큰 수를 max_val로 업데이트 + # 이때 num이 저장된다는 의미는 최대/최소곱을 해당 숫자에서 다시 시작하여 곱한다는 의미 + max_val = max(num, max_val * num, min_val * num) + + # min_val도 max_val과 동일한 원리로 저장, 이때는 미리 저장한 prev_max_val을 이용 + min_val = min(num, prev_max_val * num, min_val * num) + + # 현재까지의 결과와 이번 loop에서의 max_val을 비교하여 더 큰 수를 res로 업데이트 + res = max(res, max_val) + + return res diff --git a/minimum-window-substring/liza0525.py b/minimum-window-substring/liza0525.py new file mode 100644 index 0000000000..c0aae8c9f4 --- /dev/null +++ b/minimum-window-substring/liza0525.py @@ -0,0 +1,52 @@ +from collections import defaultdict + + +# 7기 풀이 +# 시간 복잡도: O(n) +# - s의 길이(n)만큼의 시간복잡도 +# 공간 복잡도: O(1) +# - char_dict에는 최대 26개의 알파벳만큼 저장 + 몇 개의 변수 +class Solution: + # 윈도우가 늘어나는 조건(right가 올라가는 조건): t의 알파벳 전체가 윈도우에 모두 포함될 때까지 + # 윈도우가 유지되는 조건(left가 올라가는 조건): t의 알파벳 전체가 윈도우에 포함이 된 후, 한 개라도 누락될 때까지 + def minWindow(self, s: str, t: str) -> str: + char_dict = defaultdict(int) + + # t의 구성 알파벳 정보 저장 + for tt in t: + # 알파벳 당 몇 개가 있는지 char_dict에 저장 + char_dict[tt] += 1 + # t에 있는 알파벳 종류의 개수 + need_alpha = len(char_dict) + + left = 0 + res = "" + + for right in range(len(s)): + if s[right] in char_dict: + # s[right]가 char_dict에 있다면 윈도우에 해당 알파벳이 있다는 의미로 + # char_dict에서 하나 차감 + char_dict[s[right]] -= 1 + if char_dict[s[right]] == 0: + # 해당 알파벳의 개수가 0이 되면 필요한 알파벳 중 하나를 다 찾았으므로 + # need_alpha를 하나 차감 + need_alpha -= 1 + + while need_alpha == 0: + # need_alpha가 0이라는 의미는 모든 글자를 다 찾았다는 의미 + # 윈도우를 유지하며 left를 올린다 + if not res or right - left + 1 < len(res): + # 현재의 res 글자보다 left / right 간 사이가 작을 때 res 업데이트 + res = s[left:right + 1] + if s[left] in char_dict: + # s[left]가 char_dict에 있다는 것은 left를 옮길 때 해당 알파벳은 누락이 되기 때문에 + # 다음 윈도우 구간에서는 다시 필요하므로 char_dict 내 해당 알파벳 개수를 다시 증가 + # need_alpha도 하나 증가 시켜줘야 한다. + if char_dict[s[left]] == 0: + need_alpha += 1 + char_dict[s[left]] += 1 + + # left 이동 + left += 1 + + return res diff --git a/pacific-atlantic-water-flow/liza0525.py b/pacific-atlantic-water-flow/liza0525.py new file mode 100644 index 0000000000..884d58d3de --- /dev/null +++ b/pacific-atlantic-water-flow/liza0525.py @@ -0,0 +1,37 @@ +# 7기 풀이 +# 시간 복잡도: O(m * n) +# - 최대 재귀 스택 +# 공간 복잡도: O(m * n) +# - 인덱스 모두가 대서양과 태평양에 도달할 때 최대 공간 복잡도 +class Solution: + # 문제 접근 방향 + # 각 지점에서 시작하는 것이 아닌, 태평양과 대서양에 맞닿아 있는 인덱스에서 시작해서 + # 최대 높이에 도달할 때까지 DFS로 접근 후, 두 대양에서 모두 도달하는 인덱스를 찾는다. + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] + + pacific_dest = set() # 태평양에서 시작할 때 갈 수 있는 곳 + atlantic_dest = set() # 대서양에서 시작했을 때 갈 수 있는 곳 + + def dfs(i, j, dest): + dest.add((i, j)) + for dir_i, dir_j in directions: + next_i, next_j = i + dir_i, j + dir_j + + if ( + 0 <= next_i < len(heights) + and 0 <= next_j < len(heights[0]) + and (next_i, next_j) not in dest + and heights[next_i][next_j] >= heights[i][j] # 다음 인덱스가 같거나 높은 경우만 탐색 + ): + dfs(next_i, next_j, dest) + + for j in range(len(heights[0])): + dfs(0, j, pacific_dest) # 태평양에서 시작(맨 윗줄) + dfs(len(heights) - 1, j, atlantic_dest) # 대서양에서 시작(맨 아랫줄) + + for i in range(len(heights)): + dfs(i, 0, pacific_dest) # 태평양에서 시작(맨 왼쪽 줄) + dfs(i, len(heights[0]) - 1, atlantic_dest) # 대서양에서 시작(맨 오른쪽 줄) + + return list(pacific_dest.intersection(atlantic_dest)) # 겹치는 도착지만 리턴 diff --git a/sum-of-two-integers/liza0525.py b/sum-of-two-integers/liza0525.py new file mode 100644 index 0000000000..d08062d1d0 --- /dev/null +++ b/sum-of-two-integers/liza0525.py @@ -0,0 +1,20 @@ +# 7기 풀이 +# 시간 복잡도: O(1) +# - 숫자가 32비트이기 때문에 최대 32번의 루프 +# 공간 복잡도: O(1) +# - 몇 개의 변수만 +class Solution: + # 해당 문제는 bit 연산을 이용해서 덧셈을 구현 + def getSum(self, a: int, b: int) -> int: + # 문제 조건에 두 숫자는 모두 32bit 숫자임을 명시되었으며 + # 이 mask를 사용해야 음수 대응도 가능해진다. + mask = 0xFFFFFFFF + + while b & mask: + carry = (a & b) << 1 # 올림 표현 + a = a ^ b # XOR 연산, 비트의 덧셈은 XOR과 동일하기 때문 + b = carry # 올림한 것을 다음 연산에 사용한다 + + # b가 0이 아니라면 무한 비트로 올라가고 있음을 의미하며 + # 음수 대응을 위해 a와 mask를 and 연산하여 return + return a if b == 0 else a & mask