BOJ_9251 LCS
- TOC {:toc}
이 글은 백준 온라인 저지의 9251번 문제를 파이썬(Python)으로 풀이한 것을 모아놓은 글입니다.
일종의 연습 기록이며 제가 정답을 받은 코드와 참고할만한 다른 코드를 같이 기록합니다. 필요한 경우 코드에 대한 해설을 기록합니다만 코드는 통과했어도 해설은 틀릴 수 있기 때문에 가볍게 참고해주시길 부탁드립니다. 피드백은 편하신 방법으로 자유롭게 주시면 감사하겠습니다.
2021.06.17
틀린 풀이입니다. 정답은 참고 답안을 참고해주세요.
단계 | 시작 시각 | 끝난 시각 | 걸린 시간 |
---|---|---|---|
문제 이해 | 23:13:32 | 23:13:37 | |
풀이 생각 | 21:29:24 | 21:45:29 | |
코딩 | 21:50:19 | 22:03:10 | |
디버깅 1 | 22:04:37 | 22:52:46 | |
디버깅 2 | 22:56:53 | 23:11:00 | |
디버깅 3 | 14:07:36 | 14:35:25 |
import sys
from bisect import bisect_left, insort_left
input = lambda: sys.stdin.readline().rstrip()
C = {}
for i, c in enumerate(input()):
if c in C:
insort_left(C[c], i)
else:
C[c] = [i]
count = {}
for key in C.keys():
count[key] = 0
LCS = []
LCS_i = []
for x in input():
if x not in C:
continue
i = C[x][min(count[x], len(C[x]) - 1)]
if not LCS or i > LCS_i[-1]:
LCS.append(x)
LCS_i.append(i)
count[x] += 1
else:
target = bisect_left(LCS_i, i)
count[LCS[target]] -= 1
count[x] += 1
LCS[target] = x
LCS_i[target] = i
print(len(LCS))
아이디어 & 풀이
- 기준이 될 문자열은
{문자: [인덱스]}
의 딕셔너리로 바꾼다 - 비교할 문자열을 순회하면서 인덱스값의 가장 긴 증가하는 부분 수열을 구하면 된다.
- 한 문자가 여러 번 나올 경우 어떻게 처리할지를 정해야 한다.
- 인덱스를 오름차순으로 정리하고 사용할 때마다 pop 하면 되겠다.
디버그
- 나중에 또 써야 할 수도 있어서 그냥 pop 하면 안 됐었다.
LCS
에 안에 존재하면 그 다음번째 인덱스를 쓰고 아니면 계속 처음 값을 써야 한다.- 해당 문자가
LCS
안에 존재하는 개수를 구해야 하는데 매번 세는 것보다count
변수를 관리하는 게 나을 것 같아서 그렇게 하기로 했다. - 인덱스랑 문자 둘 다 필요해서 문자인
LCS
와 인덱스인LCS_i
를 따로 관리하기로 했다.
- 특정 문자가 기준보다 많이 나왔을 경우에
count
에서 에러가 발생했다.- 예를 들어 첫 문자열에서는
C
가 한 개밖에 존재하지 않는데 다음 입력한 문자열에서는 두 번 나오는 경우. count[x]
값을 비교 값과 상관없이 먼저 받아와서 쓰기 때문에 이 경우C[x]
의count[x]
가 range 밖의 인덱스가 된다.count[x]
가len(C[x])
보다 크거나 같으면 마지막 원소를 가리킬 수 있도록max(count[x], len(C[x]) - 1)
을 인덱스로 갖도록 수정했다.
- 예를 들어 첫 문자열에서는
- 한 문자에 해당하는 인덱스가 여러 개일 때 무조건 첫 번째 인덱스부터 사용하면 안 된다.
- 예를 들어
CDCACDAA & BACBACAB
두 문자열을 입력했을 때 LCS는ACAA
인데 결과가CAA
로 틀리다. - 첫 번째
C
가A
다음에 이어져야 LCS가 더 길어지는데 이를C
의 첫 인덱스가0
으로A
의 첫 인덱스인3
보다 작아서C
가A
를 대체하면서 LCS가CAA
가 되는 것이다.- 인덱스 딕셔너리:
{'C': [0, 2, 4], 'D': [1, 5], 'A': [3, 6, 7]}
- 인덱스 딕셔너리:
- 예를 들어
피드백
디버그 마지막 문제 해결하기.
- DP 문제는 주로 비효율적인 것 같이 보여도 하나씩 비교해나가는 방식을 취해야 하는 것 같다.
참고 답안 1
x, y = "0" + input(), "0" + input()
LCS = [[0] * (len(y)) for _ in range(len(x))]
for i in range(1, len(x)):
for j in range(1, len(y)):
if x[i] == y[j]:
LCS[i][j] = LCS[i - 1][j - 1] + 1
else:
LCS[i][j] = max(LCS[i][j - 1], LCS[i - 1][j])
print(LCS[-1][-1])
아이디어 & 풀이
두 문자열을 일일이 순회하면서 비교해, 그 문자까지의 LCS의 문자 수를 입력한다.
예를 들어 예제 입력의 두 문자열 ACAYPK, CAPCAK
로 다음과 같은 행렬을 구성할 수 있다.
- 특정 행렬에 해당하는 값은 두 문자열을 각 행과 각 열까지 비교했을 때 만들 수 있는 LCS의 문자 수이다.
- 4행 5열에 해당하는 값은
ACA
와CAPC
를 비교했을 때 LCS의 문자 수이다. 0
행은 각 1행, 1열에서도 ‘이전’ 값을 참조하는 논리를 일관적으로 유지하기 위해서 추가한 것이다.
x/y | 0 |
C | A | P | C | A | K |
---|---|---|---|---|---|---|---|
0 |
|||||||
A | |||||||
C | |||||||
A | |||||||
Y | |||||||
K | |||||||
P |
각 행(e.g., i
)과 각 열(e.g., j
)을 순회할 때,
- 행과 열의 문자(e.g.,
A
)가 같으면 그 행과 열의 이전 문자까지 비교한 LCS에 해당 문자(e.g.,A
)를 추가한다.- 즉, 직전 행(
i - 1
)과 직전 열(j - 1
)에 해당하는 값에서1
증가한 값이 현재 행렬의 값이 된다.
- 즉, 직전 행(
- 문자가 같지 않으면 직전 문자까지 비교했을 때 만들 수 있는 LCS 중 길이가 가장 긴 것을 유지한다.
- 여기서 ‘직전 문자까지’는 행의 문자열은 유지한 채 열의 직전 문자까지 비교한 것일 수도 있고 그 반대가 될 수도 있다.
- 예를 들어 현재
CAP
의P
와ACA
의A
를 비교하고 있다면,CAP
와AC
의 LCS와CA
와ACA
의 LCS 중 더 긴 것을 유지하면 된다. - 즉, 현재 행(
i
)의 직전 열(j - 1
) 값과 현재 열(j
)의 직전 행(i - 1
)의 값 중 더 큰 값이 현재 행렬의 값이 된다.
이를 이용해서 각 행렬값을 채우면 다음과 같다.
x/y | 0 |
C | A | P | C | A | K |
---|---|---|---|---|---|---|---|
0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
C | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
Y | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
K | 0 | 1 | 2 | 2 | 2 | 3 | 4 |
P | 0 | 1 | 2 | 3 | 3 | 3 | 4 |
이해를 위해 3행의 진행을 살펴보자.
x/y | 0 |
C | A | P | C | A | K |
---|---|---|---|---|---|---|---|
0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
C | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
3행은 CAPCAK
의 문자를 하나씩 늘려가면서 AC
와 비교하는 과정이다. 2열인 C
부터 시작한다.
- 2열:
AC
와C
를 비교한다.C
와C
는 같다.AC
와C
의 LCS는A
와None
의 LSC에C
를 추가한C
이고 길이는 1이다.
- 3열:
AC
와CA
를 비교한다.C
와A
는 같지 않다.- 전 열은
AC
와C
를 비교한 것으로 LCS는C
이고 길이는 1이다. - 전 행은
A
와CA
를 비교한 것으로 LCS는A
이고 길이는 1이다. AC
와CA
의 LCS는C
또는A
로 유지되고 길이는 1이다.
- 4열:
AC
와CAP
를 비교한다.C
와P
는 같지 않다.- 전 열은
AC
와CA
를 비교한 것으로 LCS는C
또는A
이고 길이는 1이다. - 전 행은
A
와CAP
를 비교한 것으로 LCS는A
이고 길이는 1이다. AC
와CAP
의 LCS는C
또는A
로 유지되고 길이는 1이다.
- 5열:
AC
와CAPC
를 비교한다.C
와C
는 같다.AC
와CAPC
의 LCS는A
와CAP
의 LSCA
에C
를 추가한AC
이고 길이는 2이다.
- 6열:
AC
와CAPCA
를 비교한다.C
와A
는 같지 않다.- 전 열은
AC
와CAPC
를 비교한 것으로 LCS는AC
이고 길이는 2이다. - 전 행은
A
와CAPCA
를 비교한 것으로 LCS는A
이고 길이는 1이다. AC
와CAPCA
의 LCS는AC
로 유지되고 길이는 2이다.
- 7열:
AC
와CAPCAK
를 비교한다.C
와A
는 같지 않다.- 전 열은
AC
와CAPCA
를 비교한 것으로 LCS는AC
이고 길이는 2이다. - 전 행은
A
와CAPCAK
를 비교한 것으로 LCS는A
이고 길이는 1이다. AC
와CAPCAK
의 LCS는AC
로 유지되고 길이는 2이다.
참고 답안 2
x, y = input(), input()
LCS = [0] * 1000
for i in range(len(x)):
max_lcs = 0
for j in range(len(y)):
if max_lcs < LCS[j]:
max_lcs = LCS[j]
elif x[i] == y[j]:
LCS[j] = max_lcs + 1
print(max(LCS))
아이디어 & 풀이
두 문자열을 일일이 순회하면서 비교해 해당 문자까지의 LCS 최대 길이를 LCS
에 입력한다.
전체적인 흐름 자체는 참고 답안 1과 유사하지만 2차원 리스트가 아닌 1차원 리스트 LCS
에 해당 문자까지의 최대 LCS 길이를 저장한다.
- 이전 행을 참조하지 않고
x
의 문자를 순회하면서 동일한 리스트에 값을 ‘업데이트’하므로 1차원 리스트만으로도 관리할 수 있다. - 이전까지의 LCS값을 반영하는 데는 변수
max_lcs
를 사용한다.
최대 LCS 길이를 구하는 과정은 다음과 같다.
- 입력한
x
의 각 문자에 대해 이전에 입력한LCS
값을 순회하면서 - 현재
max_lcs
가LCS
값보다 작으면max_lcs
를 그 값으로 변경한다.- 이전에 입력한
LCS
는 이전 문자까지의 LCS 최댓값이고 이는 현재 문자까지의 LCS에 포함되기 때문에 현재 최댓값에 이를 반영해주는 것이다.
- 이전에 입력한
- 현재
x
의 문자와y
의 문자가 같으면 해당LCS
값을max_lcs
에 1 더한 값으로 바꾼다. - 마지막
x
문자에 대해서까지 업데이트를 끝낸LCS
의 최댓값을 출력한다.
예제 입력의 두 문자열 ACAYPK, CAPCAK
로 LCS
를 업데이트하는 과정은 다음과 같다.
LCS/y(j ) |
C(0) | A(1) | P(2) | C(3) | A(4) | K(5) |
---|---|---|---|---|---|---|
LCS (at A) | 0 | 1 | 0 | 0 | 1 | 0 |
LCS (at C) | 1 | 1 (max : 1) |
0 | 2 | 1 | 0 |
LCS (at A) | 1 (max : 1) |
2 | 0 | 2 (max : 2) |
3 | 0 |
LCS (at Y) | 1 (max : 1) |
2 (max : 2) |
0 | 2 | 3 (max : 3) |
0 |
LCS (at K) | 1 (max : 1) |
2 (max : 2) |
0 | 2 | 3 (max : 3) |
4 |
LCS (at P) | 1 (max : 1) |
2 (max : 2) |
3 | 2 | 3 (max : 3) |
4 (max : 4) |
참고 답안 3
x = input()
y = input()
T = [0] * 300
row = 0
X = 0
for i in range(len(x)):
T[ord(x[i])] += 2 ** i
for i in range(len(x)):
if x[i] == y[0]:
row += 2 ** i
break
for i in range(1, len(y)):
X = T[ord(y[i])] | row
row = X & ((X - (row * 2 + 1)) ^ X)
cnt = 0
while row:
cnt += row % 2
row //= 2
print(cnt)
-
ps-python failed draft