[프로그래머스] 매칭 점수

송정근·2026년 8월 20일

코딩 테스트 준비

목록 보기
89/125

문제 요약

검색어 word와 여러 웹페이지 HTML이 주어진다. 각 웹페이지의 기본 점수와 다른 페이지로부터 받은 링크 점수를 더한 매칭 점수를 계산한 뒤, 점수가 가장 높은 페이지의 인덱스를 반환한다.

  • 기본 점수: 본문 텍스트에서 word가 등장한 횟수
  • 링크 점수: 다른 페이지가 자신의 기본 점수를 외부 링크 수만큼 나누어 전달한 점수
  • 점수가 같으면 입력 순서가 앞선 페이지를 선택한다.

핵심 아이디어

페이지를 읽으면서 바로 링크 점수를 계산하면, 아직 다른 페이지의 URL이나 기본 점수를 알 수 없다. 따라서 아래처럼 두 단계로 나눈다.

  1. 모든 페이지를 분석해 URL, 기본 점수, 외부 링크 목록을 저장한다.
  2. 각 페이지의 기본 점수를 외부 링크 수로 나누어 연결된 페이지에 더한다.

URL을 키로, 페이지 인덱스를 값으로 저장하는 딕셔너리를 만들면 링크의 도착 페이지를 빠르게 찾을 수 있다.

문자열 처리

기본 점수에서 검색어는 알파벳이 아닌 문자로 양쪽이 구분되어야 한다. 예를 들어 MuziMuzi는 muzi가 두 번 등장한 것으로 세면 안 된다.

본문에서 알파벳으로 이루어진 단어만 추출한 뒤 검색어와 비교하면 이 조건을 자연스럽게 처리할 수 있다.

words = re.findall(r"[a-z]+", text.lower())
basic_score = sum(token == word for token in words)

<body> 영역만 가져오고 HTML 태그를 공백으로 바꾼 뒤 단어를 추출한다. 이렇게 하면 URL이나 태그 속성에 포함된 문자열은 기본 점수에 포함되지 않는다.

풀이 과정

  1. 각 페이지의 대표 URL을 추출해 url_to_index에 저장한다.
  2. 본문 텍스트에서 검색어의 등장 횟수를 계산한다.
  3. 페이지의 모든 외부 링크를 저장한다.
  4. 각 페이지의 기본 점수 / 외부 링크 수를 연결된 페이지에 더한다.
  5. 매칭 점수가 가장 큰 인덱스를 찾는다. 동점일 때 앞선 인덱스를 유지하기 위해 >일 때만 답을 갱신한다.

Python 코드

import re


def solution(word, pages):
    word = word.lower()
    page_count = len(pages)

    # URL -> pages에서의 인덱스
    url_to_index = {}
    basic_scores = [0] * page_count
    outgoing_links = [[] for _ in range(page_count)]

    # 1단계: 각 페이지의 정보 수집
    for index, page in enumerate(pages):
        url = re.search(
            r'<meta property="og:url" content="([^"]+)"',
            page
        ).group(1)
        url_to_index[url] = index

        body = re.search(
            r"<body>(.*?)</body>",
            page,
            re.DOTALL | re.IGNORECASE
        ).group(1)

        # 태그를 제외한 본문 텍스트에서 알파벳 단어만 추출한다.
        text = re.sub(r"<[^>]+>", " ", body).lower()
        words = re.findall(r"[a-z]+", text)
        basic_scores[index] = sum(token == word for token in words)

        # 현재 페이지가 가리키는 모든 외부 링크
        outgoing_links[index] = re.findall(r'<a href="([^"]+)"', page)

    # 기본 점수로 매칭 점수를 초기화한다.
    matching_scores = [float(score) for score in basic_scores]

    # 2단계: 각 페이지의 링크 점수를 목적지 페이지에 분배한다.
    for source_index, links in enumerate(outgoing_links):
        if not links:
            continue

        shared_score = basic_scores[source_index] / len(links)

        for url in links:
            destination_index = url_to_index.get(url)

            # 입력으로 주어진 페이지에 없는 URL은 점수를 받을 대상이 없다.
            if destination_index is not None:
                matching_scores[destination_index] += shared_score

    # 동점이면 먼저 등장한 페이지를 유지한다.
    answer = 0
    for index in range(1, page_count):
        if matching_scores[index] > matching_scores[answer]:
            answer = index

    return answer

코드 설명

  • url_to_index: 외부 링크 URL이 어느 페이지를 가리키는지 찾는다.
  • basic_scores: 각 페이지가 직접 얻은 검색어 등장 횟수다.
  • outgoing_links: 각 페이지가 다른 페이지로 보내는 링크 목록이다.
  • shared_score: 현재 페이지가 링크 하나에 전달하는 점수다. 이 값은 매칭 점수가 아니라 기본 점수를 기준으로 계산한다.
  • matching_scores: 기본 점수와 다른 페이지에서 받은 링크 점수의 합이다.

예를 들어 어떤 페이지의 기본 점수가 6이고 외부 링크가 3개라면, 링크 하나당 6 / 3 = 2점씩 전달한다.

시간 복잡도

  • L: 모든 HTML 문자열 길이의 합
  • E: 모든 외부 링크 개수의 합

정규식으로 HTML을 분석하는 데 O(L), 링크 점수를 분배하는 데 O(E)가 걸린다.

  • 시간 복잡도: O(L + E)
  • 공간 복잡도: O(P + E) (P는 페이지 수)

정리

이 문제의 핵심은 페이지 정보를 먼저 전부 수집한 뒤 링크 점수를 분배하는 것이다. 또한 동점 처리 조건 때문에 최댓값을 찾을 때는 >=가 아니라 >를 사용해야 한다.

profile
기록하며 성장하는 개발자

0개의 댓글