Skip to content

[thispath98] Week 4 #821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions coin-change/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
"""
Intuition:
dp 배열에 이전 금액에 대한 최소 개수를 저장해두고
갱신하는 방식으로 작동한다.

for 루프를 돌면서 현재 가격에서 coin만큼의 가격을
뺐을 때 거슬러줄 수 있다면, 그 값에서 1개를 더해준
개수를 prev_coins에 저장한다.

이후 prev_coins가 존재하면 현재 인덱스에서 거슬러줄 수 있는
동전의 최소 개수를 갱신한다.

Time Complexity:
O(amount x coins.length):
amount 만큼 루프를 순회하는데 각 루프마다
coins.length 만큼 prev_coins 배열을 만든다.

Space Complexity:
O(amount):
amount만큼의 크기를 가지는 dp 배열을 저장한다.
"""
dp = [0 for _ in range(amount + 1)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dp의 요소 초기값을 0이 아니라 최대값으로 주면 어떨까요?
저도 이번에 배웠는데, 최소 개수 구하는 문제라서 최대값으로 초기화하니까 if문도 줄고, 로직이 더 명확해지더라구요!!


for coin in coins:
if coin <= amount:
dp[coin] = 1

for i in range(1, amount + 1):
if dp[i]:
continue

prev_coins = [dp[i - coin] + 1 for coin in coins if i >= coin and dp[i - coin] > 0]
if prev_coins:
dp[i] = min(prev_coins)

answer = -1 if amount > 0 and dp[amount] == 0 else dp[amount]
return answer
86 changes: 86 additions & 0 deletions merge-two-sorted-lists/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoListsList(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
"""
Intuition:
두 리스트의 원소를 각각 비교하면서 한번씩 스캔한다.
결과적으로 한번씩만 스캔하면 정렬할 수 있다.

Time Complexity:
O(N):
두개의 리스트를 1번 순회하며 답을 찾으므로,
O(N)의 시간복잡도가 소요된다.

Space Complexity:
O(N):
sorted_list에 정렬된 배열을 저장하므로,
O(N)의 공간복잡도가 소요된다.
"""
sorted_list = []
while list1 is not None and list2 is not None:
if list1.val < list2.val:
sorted_list.append(list1.val)
list1 = list1.next
else:
sorted_list.append(list2.val)
list2 = list2.next

while list1 is not None:
sorted_list.append(list1.val)
list1 = list1.next
while list2 is not None:
sorted_list.append(list2.val)
list2 = list2.next

sorted_node = None
while sorted_list:
val = sorted_list.pop()
sorted_node = ListNode(val, sorted_node)

return sorted_node

def mergeTwoListsNode(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
"""
Intuition:
파이썬 리스트를 사용하지 않고
주어진 ListNode로부터 바로 시작한다.

Time Complexity:
O(N):
두개의 리스트를 1번 순회하며 답을 찾으므로,
O(N)의 시간복잡도가 소요된다.

Space Complexity:
O(1):
ListNode를 바로 사용하므로
상수 만큼의 O(1)의 공간복잡도가 소요된다.

Key takeaway:
링크드 리스트를 오랜만에 접하니 잘 풀지 못했던 것 같다.
전통적인 자료구조를 OOP 관점으로 고민해보자.
"""
sorted_node = ListNode()
current_node = sorted_node

while True:
if list1 is None:
current_node.next = list2
break
elif list2 is None:
current_node.next = list1
break
Comment on lines +69 to +75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while True로 주셨는데, 위의 코드에 남겨주신 것처럼 조건 명시하면 더 좋을 것 같아요 ~
while list1 is not None and list2 is not None:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

감사합니다. 무한 루프는 최소화해보겠습니다!


if list1.val < list2.val:
current_node.next = ListNode(list1.val)
current_node = current_node.next
list1 = list1.next
else:
current_node.next = ListNode(list2.val)
current_node = current_node.next
list2 = list2.next

return sorted_node.next
23 changes: 23 additions & 0 deletions missing-number/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Solution:
def missingNumber(self, nums: List[int]) -> int:
"""
Intuition:
주어진 리스트의 개수를 얻어 범위를 구한다.
이후 세트를 이용해서 범위 내의 정수가
세트 안에 없으면 그 수를 리턴한다.

Time Complexity:
O(N):
세트(해시)는 접근하는 데에 상수의 시간이 걸리므로
최대 N + 1번의 접근을 하므로
O(N)의 시간복잡도가 소요된다.

Space Complexity:
O(N):
리스트를 해시로 변환하여 저장하고 있으므로
O(N)의 공간복잡도가 소요된다.
"""
num_set = set(nums)
for i in range(len(nums) + 1):
if i not in num_set:
return i
Comment on lines +20 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 직관적이라서 정말 좋아요 👍

130 changes: 130 additions & 0 deletions palindromic-substrings/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
class Solution:
def countSubstrings(self, s: str) -> int:
"""
Intuition:
2중 루프를 돌면서 각 substring에 대해
palindrome인지 아닌지 확인한다.
한번 palindrome인지 확인했으면, set에 추가하여
중복 확인을 한다.

Time Complexity:
O(N^2 x s.length):
2중 루프는 N^2만큼 소요되고,
각 루프에 palindrome을 체크하는 것은
s.length만큼 소요된다.

Space Complexity:
O(N^2):
palindrome이 모두 중복되지 않을 경우 set에
s의 substring 개수만큼 저장한다.
이는 대략 N^2이다.
"""
def is_palindrome(s):
return s == s[::-1]

palindrome_set = set()
answer = 0
for i in range(1, len(s) + 1):
for j in range(0, len(s) - i + 1):
substr = s[j: j + i]
if substr in palindrome_set or is_palindrome(substr):
palindrome_set.add(substr)
answer += 1
return answer


class SolutionDPSet:
def countSubstrings(self, s: str) -> int:
"""
Intuition:
위 solution에서 중복을 제거할 수 있는 방법은,
start : end 길이를 갖는 substring에서
s[start] == s[end]이고, start + 1 : end - 1의
substring이 palindrome이라면, 이 substring은
palindrome이라고 볼 수 있다.

Time Complexity:
O(N^2):
DP로 인해 palindrome을 찾는 함수가 대략
상수의 시간복잡도가 걸린다고 볼 수 있다.
따라서 substring을 만드는 이중 루프에서의
시간복잡도가 걸릴 수 있다고 보면 된다.

Space Complexity:
O(N^2):
dp set에 index set을 저장하는데, 최악의 경우
index set은 N^2개만큼 저장될 수 있다.

Key takeaway:
dp를 이용해서 푸는 방식에 대해 익숙해져야겠다.

의문점은 leetcode에서 bruteforce보다 시간이 더 소요되었다는 것이다.
아마 list 크기를 초과할 경우에 append를 할 경우,
리스트 크기를 2배만큼 늘리는 list doubling 방식이
set에도 적용이 되어 느려진 것으로 보인다.
"""
dp = set()


def is_palindrome(start, end):
while start < end:
if s[start] != s[end]:
return False
if (start, end) in dp:
return True
start += 1
end -= 1

return True


answer = 0
for length in range(1, len(s) + 1):
for start in range(0, len(s) - length + 1):
end = start + length - 1
if (start, end) in dp or is_palindrome(start, end):
dp.add((start, end))
answer += 1
return answer


class SolutionDPList:
def countSubstrings(self, s: str) -> int:
"""
Intuition:
DP solution에서 set로 저장하지 않고,
이중 리스트로 저장하는 것으로 수정했다.
length = 2인 경우에는 start와 end만 동일하면
palindrome으로 판단할 수 있어 조건을 추가했다.

Time Complexity:
O(N^2):
DP로 인해 palindrome을 찾는 함수가 대략
상수의 시간복잡도가 걸린다고 볼 수 있다.
따라서 substring을 만드는 이중 루프에서의
시간복잡도가 걸릴 수 있다고 보면 된다.

Space Complexity:
O(N^2):
dp 리스트에 substring 이중 리스트를 저장하므로
N^2개만큼 저장될 수 있다.

Key takeaway:
이 방법이 가장 빠르게 동작했다.
"""
dp = [[False for _ in s] for _ in s]
answer = 0

for i in range(len(s)):
dp[i][i] = True
answer += 1

for length in range(2, len(s) + 1):
for start in range(len(s) - length + 1):
end = start + length - 1
if s[start] == s[end]:
if length == 2 or dp[start + 1][end - 1]:
dp[start][end] = True
answer += 1

return answer
48 changes: 48 additions & 0 deletions word-search/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
"""
Intuition:
보드를 돌면서 dfs를 수행한다.
dfs는 상하좌우를 돌면서 word의 index와
board의 word가 동일한지 확인한다.

Time Complexity:
O(M x N + w.length^4):
M x N 크기의 배열을 돌면서,
각 칸마다 상하좌우 4번씩 확인한다.
최대 word length번만큼 반복한다.

Space Complexity:
O(M x N + w.length):
M x N 크기의 visited 배열을 초기화하고,
dfs의 호출 스택은 word length만큼 반복한다.
"""
visited = [[False for _ in board[0]] for _ in board]

def dfs(y, x, index):
if index == len(word):
return True
if not (0 <= y < len(board) and 0 <= x < len(board[0])):
return False
if visited[y][x]:
return False
if word[index] != board[y][x]:
return False

visited[y][x] = True
for dy, dx in [[-1, 0], [1, 0], [0, -1], [0, 1]]:
ny = y + dy
nx = x + dx

if dfs(ny, nx, index + 1):
return True

visited[y][x] = False
return False

for i in range(len(board)):
for j in range(len(board[0])):
if dfs(i, j, 0):
return True

return False
Loading