게으른개발너D

[JS] DP - 단어 퍼즐 본문

알고리즘/문제

[JS] DP - 단어 퍼즐

lazyhysong 2023. 8. 3. 17:27

https://school.programmers.co.kr/learn/courses/30/lessons/12983

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

단어 퍼즐은 주어진 단어 조각들을 이용해서 주어진 문장을 완성하는 퍼즐입니다. 이때, 주어진 단어 조각들은 각각 무한개씩 있다고 가정합니다. 예를 들어 주어진 단어 조각이 [“ba”, “na”, “n”, “a”] 경우 "ba", "na", "n", "a" 단어 조각이 각각 무한개씩 있습니다. 이때, 만들어야 하는 문장이 “banana”라면 “ba”, “na”, “n”, “a” 4개를 사용하여 문장을 완성할 있지만, “ba”, “na”, “na” 3개만을 사용해도 “banana” 완성할 있습니다. 사용 가능한 단어 조각들을 담고 있는 배열 strs 완성해야 하는 문자열 t 매개변수로 주어질 , 주어진 문장을 완성하기 위해 사용해야 하는 단어조각 개수의 최솟값을 return 하도록 solution 함수를 완성해 주세요. 만약 주어진 문장을 완성하는 것이 불가능하면 -1 return 하세요.

 

제한 사항

  • strs는 사용 가능한 단어 조각들이 들어있는 배열로, 길이는 1 이상 100 이하입니다.
  • strs의 각 원소는 사용 가능한 단어조각들이 중복 없이 들어있습니다.
  • 사용 가능한 단어 조각들은 문자열 형태이며, 모든 단어 조각의 길이는 1 이상 5 이하입니다.
  • t는 완성해야 하는 문자열이며 길이는 1 이상 20,000 이하입니다.
  • 모든 문자열은 알파벳 소문자로만 이루어져 있습니다.

입출력 예

strs t result
["ba","na","n","a"] "banana" 3
["app", "ap", "p", "l", "e", "ple", "pp"] "apple" 2
["ba", "an", "nan", "ban", "n"] "banana" -1

 


✨ Solution

문제 설명만 읽었을 때는 순열이나 백트래킹을 통해 풀 수 있을 것처럼 보인다. 하지만 순열은 시간복잡도가 매우 크기 때문에 사용하기 힘들고, 백트래킹을 통해 가지치기를 하더라도 제한 시간을 통과하기는 힘들어보인다.

이런 경우엔 동적 계획법으로 해결할 수 있을지 확인해 봐야 한다.

 

1. 문제를 어떻게 정의할지 정하기 (가장 작은 문제 정의하기)

이 문제의 경우 필요한 단어 개수의 최소값을 구해야 하기 때문에, 가장 작은 문제도 단어 개수로 정의하는 것이 좋다.

예를들어, 입출력 예로 들어온 ["ba", "na", "n", "a"], "banana"의 경우, 가장 작은 문제를 다음과 같이 정의할 수 있다.

단어 "b"를 만들 수 있는 단어 개수의 최솟값은?
단어 "ba"를 만들 수 있는 단어 개수의 최솟값은?
단어 "ban"를 만들 수 있는 단어 개수의 최솟값은?
단어 "bana..."를 만들 수 있는 단어 개수의 최솟값은?

가장 작은 문제를 정의했다면 작은 문제를 통해 큰 문제의 답을 알 수 있는지 확인해야 한다.

 

2. 작은 문제로 큰 문제의 답을 알 수 있는지 확인하기

단어 "ba"나 단어 "ban"의 단어 개수 최솟값을 이전 답을 통해 알 수 있을까?

 

먼저 단어 "b"부터 확인해 보자.

단어 "b"의 경우 ["ba", "na", "n", "a"]에서 가능한 단어조각이 없기 때문에 만드는 것이 불가능하다. 따라서 -1이다.

 

단어 "ba"["b", "a"]"ba"로 확인할 수 있다.

하지만 "b"는 단어 조각으로 만들 수 없기 때문에 "ba"로 확인한다.

확인해보면 ["ba", "na", "n", "a"]에서 1개 단어 조각으로 구성이 가능하다. 따라서 값은 1이다.

 

단어 "ban"["b", "an"], ["ba", "n"], "ban"으로 나눠서 확인할 수 있다.

첫 번째의 경우 "b"가 -1이기 때문에 생략하고 넘어간다.

두 번째 ["ba", "n"]에서 "ba"는 이미 1인 것을 구했기 때문에(메모이제이션) "n"만 구하면 된다.

"n"은 1개 단어 조각으로 구성이 가능하다.

따라서 합치면 2개 단어 조각으로 "ban"을 구성할 수 있다. 현재 "ban"의 단어 개수 최솟값은 2이다.

"ban"은 가능한 단어 조각이 없기에 생략한다.

 

단어 "bana"["b", "ana"], ["ba", "na"], ["ban", "a"], "bana"로 나눠서 확인할 수 있다.

되는 것은 ["ba", "na"]이다.

"ba"는 이미 메모이제이션을 통해 1임을 알기에 "na"만 확인하면 된다.

"na"는 1개 단어 조각으로 구성이 가능하기에 "bana"는 단어 개수 최솟값이 2가 된다.

 

단어 "banan"은 되는 것만 보면 ["bana", "n"]이다.

"bana"는 이미 2인 것을 알기에 "n"만 더해서 3이 된다.

 

마지막으로 단어 "banana"는 되는 것만 확인하면 ["bana", "na"]["banan", "n"]이다.

["bana", "na"]은 각각 2와 1이 더해져 3이지만 ["banan", "n"]은 3과 1이 더해져 4이기 때문에 더 작은 값인 ["bana", "na"]을 선택한다.

 

이런 방식으로 동적 계획법이 가능하다는 것을 확인할 수 있다.

 

 

결과 코드

실제 코드로 나타내면 어렵다..!!!

주석을 보면서 공부하자!

function solution(strs, t) {
    // 편의를 위해 t의 길이 + 1만큼 배열을 만든다.
    const dp = Array.from({ length: t.length + 1 }, () => 0);
    // 문자열 검사를 빠르게 하기 위해서 문자열 리스트를 set으로 만든다.
    const strsSet = new Set(strs);

    // 1부터 문자열 길이 + 1까지 루프를 돈다.
    for (let i = 1; i < t.length + 1; i += 1) {
        // 일단 해당 문자열의 최솟값은 무한으로 설정한다.
        dp[i] = Infinity;
        // 문자열을 자르면서 단어 조각을 찾기 위해 루프를 돈다.
        // 단어 조각은 5 이하기 때문에 마지막까지 자를 필요는 없다.
        for (let j = 1; j < Math.min(i + 1, 6); j += 1) {
            const start = i - j;
            const end = i;
            // 단어 조각이 있다면
            if (strsSet.has(t.slice(start, end))) {
                // 이전 조합과 더해서 최솟값인지 체크 후 대입한다.
                dp[i] = Math.min(dp[i], dp[i - j] + 1);
            }
        }
    }

    // 결과적으로 단어의 최솟값을 구할 수 있다. 만약 무한이라면 불가능한 조합이기 때문에 -1을 리턴한다.
    return dp[dp.length - 1] === Infinity ? -1 : dp[dp.length - 1];
}

'알고리즘 > 문제' 카테고리의 다른 글

[JS] 최소공배수 구하기 - N개의 최소공배수  (0) 2023.09.01
[JS] BitMask - 비밀지도  (0) 2023.08.03
[JS] Backtracking - N-Queen  (0) 2023.08.02
[JS] Two Pointer - 보석 쇼핑  (0) 2023.08.01
[JS] Kruskal - 섬 연결하기  (0) 2023.08.01
Comments