모든 기록
Data Structure & Algorithm · 2026.09.09

3주차 - LCS(최장 공통 부분수열)를 DP 테이블로 구하기

두 문자열을 각각 행과 열에 두고, 같으면 대각선 위에 1을 더하고 다르면 위/왼쪽 중 최댓값을 가져오는 방식으로 LCS 길이를 채워나가는 DP 테이블 구현.

3주차 - LCS(최장 공통 부분수열)를 DP 테이블로 구하기 대표 이미지
def lcs_length(s1: str, s2: str) -> int:
    """
    s1, s2 의 가장 긴 공통 부분수열의 길이를 반환.
    어느 한쪽이라도 비어 있으면 0 을 반환합니다.
    """
    if s1 == "" or s2 == "": return 0
 
    arr = [[0 for _ in range(len(s1))] for idx in range(len(s2))]
 
    for idx in range(len(s2)):
        for jdx in range(len(s1)):
            if idx == 0 and jdx == 0:
                if s2[idx] == s1[jdx]: arr[idx][jdx] = 1
            elif idx == 0:
                if s2[idx] == s1[jdx]:
                    arr[idx][jdx] = 1
                else:
                    arr[idx][jdx] = arr[idx][jdx - 1]
            elif jdx == 0:
                if s2[idx] == s1[jdx]:
                    arr[idx][jdx] = 1
                else:
                    arr[idx][jdx] = arr[idx - 1][jdx]
            else:
                if s2[idx] == s1[jdx]:
                    arr[idx][jdx] = arr[idx - 1][jdx - 1] + 1
                else:
                    arr[idx][jdx] = max(arr[idx - 1][jdx], arr[idx][jdx - 1])
    return arr[-1][-1]
 
if __name__ == "__main__":
    print("[테스트 1] 한쪽이 빈 문자열")
    print(f'  s1="", s2="abc" -> LCS 길이={lcs_length("", "abc")}')
    print()
 
    print("[테스트 2] 두 문자열이 동일")
    print(f'  s1="abc", s2="abc" -> LCS 길이={lcs_length("abc", "abc")}')
    print()
 
    print("[테스트 3] 공통 원소가 전혀 없음")
    print(f'  s1="abc", s2="xyz" -> LCS 길이={lcs_length("abc", "xyz")}')
    print()
 
    print("[테스트 4] 표준 예시 1")
    print(f'  s1="abcde", s2="ace" -> LCS 길이={lcs_length("abcde", "ace")}')
    print()
 
    print("[테스트 5] 표준 예시 2")
    print(f'  s1="AGGTAB", s2="GXTXAYB" -> LCS 길이={lcs_length("AGGTAB", "GXTXAYB")}')
    print()
 
    print("[테스트 6] 두 LCS 후보가 길이가 같은 경우")
    print(f'  s1="ABCBDAB", s2="BDCABA" -> LCS 길이={lcs_length("ABCBDAB", "BDCABA")}')
 

부분 수열이란

주어진 수열에서 일부 항을 골라내어 원래의 순서대로 나열하여 만든 새로운 수열

지금 위 코드에서는 만들어질 수 있는 부분 수열 중 오름차순으로 정렬된 가장 긴 수열을 최장 증가 부분 수열 수열이라한다. 거기서 입력으로 들어올 2 문장에 대하여 공통인 것을 뽑는다.

위를 설명하면 각 두 문자열 S1S2를 기준으로 한쪽은 행, 한쪽은 열 값을 갖게 된다.

이후 행과 열 혹은 행 또는 열이 0인 경우를 따로 분류하여 처리한다. 그러면 기본 상태가 만들어지고 이후 진행하게 되는데 비교는 만약 두 문자열이 같다면 대각선 위와 비교하여 1을 추가하고 같지 않다면 위 혹은 왼쪽 옆의 값 중 최대값을 뽑아오게 된다.

이렇게 뽑는 이유는 각 행과 각 열의 요소가 갖는 의미이기 때문이다.

결론적으로는 이전의 최대값을 의도적으로 심판대에 올리는 행위이고 그 값을 지금의 내가 선택하냐 마냐를 이번 단계의 일치로 평가하는 것이기 때문이다.

그렇기 때문에 마지막 배열의 끝 값을 가져오는 것도 결론적으로는 끝까지 다 봤을 때 내가 선택한 값은 이거다 인 값을 가져온다고 생각한다.

이어 읽으면 좋은 기록

3주차 - 재귀로 짜는 이진 트리 순회, 그리고 BST가 성능을 보장하는 이유

전위/중위/후위 순회를 재귀로 구현하며 결과 배열을 함수 밖에 두는 대신 중첩 함수로 감싸는 방법부터, 이진 트리 구조 자체는 탐색 성능을 보장하지 않는다는 것, 균형이 무너지면 O(log n)이 O(n)이 되는 이유, 캐시 지역성 때문에 DB가 이진 트리 대신 B+트리를 쓰는 이유까지.

3주차 - 그래프 탐색, BFS와 DFS를 직접 구현하며 비교

인접 리스트로 무방향/방향 그래프를 만드는 것부터, Queue로 너비 우선 탐색하는 BFS와 재귀로 깊이 우선 탐색하는 DFS를 각각 구현하고 장단점을 비교. 시간 복잡도는 둘 다 O(V+E).