Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- Python
- Medium
- Array
- math
- Depth-first Search
- 문자열
- leetcode
- DP
- dfs
- matrix
- 쉬움
- sorting
- string
- two pointers
- list
- tree
- backtracking
- 이진트리
- recursive
- 리트코드
- hash table
- binary search
- HashTable
- 미디움
- easy
- 중간
- 재귀
- linked list
- binary tree
- Binary
Archives
- Today
- Total
부부의 코딩 성장 일기
LeetCode 97(Interleaving String, Python) 본문
1. 문제 링크
LeetCode - The World's Leading Online Programming Learning Platform
Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.
leetcode.com
2. 문제 설명
- 두 개의 문자열이 주어질 때, 두 문자열이 교차로 섞여서 주어진 문자열 목표 문자열을 형성할 수 있는지 판단하여 True, False를 반환하는 문제
- 예시) s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac”가 주어지면 s1, s2를 적당히 잘라 교차로하면 aa/dbbc/bc/a/c 가 되어 s3를 만들 수 있으므로 True
3. 처음 풀이
- 한 덩어리 씩 말고 그냥 두 문자열 s1, s2에서 한 글자 씩 가져와서 s3를 만들 수 있는 지 보면 된다.
- s1의 첫 글자만 s3 첫 글자와 같으면 s1, s3의 첫 글자를 빼고 나머지에 대해 재귀
- s2의 첫 글자만 s3 첫 글자와 같으면 s2, s3의 첫 글자를 빼고 나머지에 대해 재귀
- s1, s2의 첫 글자가 같은 경우 s3 첫 글자와 다르면 False, 같으면 s1, s3의 첫 글자 빼고 재귀와 s2, s3의 첫 글자 빼고 재귀 두 가지를 실행
- s1, s2의 첫 글자 중 s3의 첫 글자와 같은게 없으면 False
- 종결 조건: s1, s2를 첫 글자를 하나 씩 제거하다보면 둘 중 하나가 먼저 바닥이 난다. 이때 남아있는 문자열이 남은 s3와 같아야 한다. 이를 통해 True, False 반환
- 이렇게만 하면 Time Limit Error가 뜬다. 따라서 True, False 반환 직전 이 결과를 딕셔너리에 저장해두고 처음 따질 때 이 딕셔너리에 저장된 결과인 지 확인해보기.
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
hist = {} # 성공적인 계산 결과 저장하는 딕셔너리
hist_no = {} # 실패한 계산 결과 저장하는 딕셔너리
def helper(s1, s2, s3):
if len(s1) + len(s2) > len(s3):
return False
if len(s1) + len(s2) < len(s3):
return False
# 이전에 처리된 부분 문자열인지 확인
if (s1, s2) in hist:
if hist[(s1, s2)] == s3:
return True
if (s2, s1) in hist:
if hist[(s2, s1)] == s3:
return True
if (s1, s2) in hist_no:
if hist_no[(s1, s2)] == s3:
return False
if (s2, s1) in hist_no:
if hist_no[(s2, s1)] == s3:
return False
# 끝 부분 도달하여 하나의 문자열이 바닥난 경우
if s1 == '':
# s1이 비어있을 때, s2와 s3가 일치하는지 확인
if s2 == s3:
hist[(s1, s2)] = s3
hist[(s2, s1)] = s3
return True
else:
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return False
if s2 == '':
# s2가 비어있을 때, s1과 s3가 일치하는지 확인
if s1 == s3:
hist[(s1, s2)] = s3
hist[(s2, s1)] = s3
return True
else:
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return False
# 부분 문자열이 교차로 섞이는지 확인
if s1[0] == s2[0]:
# s1과 s2의 첫 번째 문자가 같을 때
if s1[0] != s3[0]:
# s1과 s2의 첫 번째 문자가 s3의 첫 번째 문자와 같지 않으면 실패
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return False
else:
# s1의 첫 번째 문자를 넣는 경우와 s2의 첫 번째 문자를 넣는 경우
# 두 가지 경우를 확인하며 재귀적으로 진행
result = helper(s1[1:], s2, s3[1:]) or helper(s1, s2[1:], s3[1:])
if result:
# 성공적으로 교차로 섞인 경우, 결과를 저장
hist[(s1, s2)] = s3
hist[(s2, s1)] = s3
else:
# 실패한 경우도 저장
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return result
elif s1[0] == s3[0]:
# s1의 첫 번째 문자와 s3의 첫 번째 문자가 같은 경우
result = helper(s1[1:], s2, s3[1:])
if result:
hist[(s1, s2)] = s3
hist[(s2, s1)] = s3
else:
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return result
elif s2[0] == s3[0]:
# s2의 첫 번째 문자와 s3의 첫 번째 문자가 같은 경우
result = helper(s1, s2[1:], s3[1:])
if result:
hist[(s1, s2)] = s3
hist[(s2, s1)] = s3
else:
hist_no[(s1, s2)] = s3
hist_no[(s2, s1)] = s3
return result
return False
return helper(s1, s2, s3)
4. 다른 풀이
- 2차원 DP를 이용한다.
- dp[i][j]가 s1[:i]와 s2[:j]로 s3[:i+j]를 만들 수 있는 지 없는 지를 나타낸다고 하자.
- 그렇다면 dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])임을 이용하여 2차원 행렬을 모두 채울 수 있고 가장 오른쪽 끝 값을 반환하면 된다.
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n, l = len(s1), len(s2), len(s3)
if m + n != l:
return False
dp = [[False] * (n + 1) for _ in range(m + 1)]
dp[0][0] = True
for i in range(1, m + 1):
dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
for j in range(1, n + 1):
dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
for i in range(1, m + 1):
for j in range(1, n + 1):
dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])
return dp[m][n]
- 1차원 DP를 이용하는 풀이
- 2차원 DP 풀이에서 dp[i][j]의 값은 dp[i-1][j]와 dp[i][j-1]에 의해 정해짐을 알 수 있다. 따라서 2차원 행렬을 모두 만들 필요 없고 필요한 것만 기록하여 1차원 DP를 할 수 있다.
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n, l = len(s1), len(s2), len(s3)
if m + n != l:
return False
if m < n:
return self.isInterleave(s2, s1, s3)
dp = [False] * (n + 1)
dp[0] = True
for j in range(1, n + 1):
dp[j] = dp[j-1] and s2[j-1] == s3[j-1]
for i in range(1, m + 1):
dp[0] = dp[0] and s1[i-1] == s3[i-1]
for j in range(1, n + 1):
dp[j] = (dp[j] and s1[i-1] == s3[i+j-1]) or (dp[j-1] and s2[j-1] == s3[i+j-1])
return dp[n]
- 재귀 이용한 풀이
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
m, n, l = len(s1), len(s2), len(s3)
if m + n != l:
return False
memo = {}
def helper(i: int, j: int, k: int) -> bool:
if k == l:
return True
if (i, j) in memo:
return memo[(i, j)]
ans = False
if i < m and s1[i] == s3[k]:
ans = ans or helper(i + 1, j, k + 1)
if j < n and s2[j] == s3[k]:
ans = ans or helper(i, j + 1, k + 1)
memo[(i, j)] = ans
return ans
return helper(0, 0, 0)
5. 배운점
- 항상 DP를 떠올리기 힘들다.
- 부분을 해결하는 것이 전체를 풀 때 도움이 된다면 DP를 떠올려 보자.
'Algorithm > LeetCode' 카테고리의 다른 글
LeetCode 98(Validate Binary Search Tree, Python) (0) | 2024.02.19 |
---|---|
LeetCode 257(Binary Tree Paths, Python) (0) | 2024.02.18 |
LeetCode 242(Valid Anagram, Python) (0) | 2024.02.16 |
LeetCode 234(Palindrome Linked List, Python) (0) | 2024.02.15 |
LeetCode 232(Implement Queue using Stacks) (0) | 2024.02.14 |