Skip to content

Conversation

@hyeokbini
Copy link
Collaborator

@hyeokbini hyeokbini commented Aug 13, 2025

🔗 문제 링크

모음사전
LV.2

✔️ 소요된 시간

30분

✨ 수도 코드

민준님이 프로그래머스에 좋은 문제들이 많다고 개인적으로 추천해주셔서, 예전에 프로그래머스에서 문제를 쭉 풀다가 막혀서 백준으로 넘어왔던 문제를 다시 시도해 봤습니다.

문제에서 요구하는 사항은 특정 단어가 A,E,I,O,U로만 사용하는 사전에서 사전순으로 몇 번째에 있는지를 구하는 것입니다.

우선 조금 적어보면서 규칙성을 파악해보려 했습니다.

1 A	
2 AA
3 AAA
4 AAAA
5 AAAAA
6 AAAAE
7 AAAAI
8 AAAAO
9 AAAAU
10 AAAEA
11 AAAEE
12 AAAEI
13 AAAEO
14 AAAEU
...

구조적으로 각 인덱스에서 전에 썼던 구조를 다시 사용하는 경향성이 있는 것 같아서, 재귀함수를 이용한 DFS로 구현하면 괜찮을 것 같다는 생각이 들었습니다.

재귀함수 인자로는 현재 깊이에서 만들어진 string을 전달했고, 기저 조건으로는 문자열의 길이가 5 초과인지, 혹은 체크하려는 문자열과 같은지를 확인하는 조건을 달았습니다.

재귀호출 시에는 현재 문자열 + {A,E,I,O,U} 각각 1개를 붙여 다음 깊이로 내려보내는 식으로 구조를 설계했습니다.

간단한 시행착오를 겪으며 알아낸 주의해야 할 점은, 전역변수로 재귀함수가 호출된 횟수인 count를 조작할 때 함수의 초기 인자가 빈 문자열이라면 그 호출 또한 count가 늘어나기 때문에 변수를 -1에서 시작해야 한다는 점입니다.

📚 새롭게 알게된 내용

예전에 PS를 시작해서 아무것도 몰랐던 때에는 아이디어도 전혀 떠올리지 못해서 풀지 못하고 백준으로 넘어가게 된 계기였던 문제였는데, 지금 읽어보니 쉽게 떠올릴 수 있었고 구현도 쉽게 해낸 것 같아서 그때보단 조금 성장했다는 느낌이 드는 것 같습니다.

@mj010504
Copy link
Collaborator

예전에 고생했던 기억이 나는 문제네요. 실력이 부족할 때 왜 못풀었냐 생각해보면 문제들을 복잡하게 생각하려 했던 경향이 있어서 그랬던 것 같습니다. 대부분은 완전탐색으로 풀리는 경우도 많은 것 같아요. 수고하셨습니다!

모음사전

``cpp

#include<bits/stdc++.h>

using namespace std;
int res = 0;
int cnt = -1;
string target;
string alpha = "AEIOU";
void dfs(string word) {
cnt++;

if(word == target) {
    res = cnt;
    return;
}

if(word.length() >=5) return;

for(int i = 0; i < 5; i++) {
    dfs(word + alpha[i]);
}

}

int solution(string word) {

target = word;
dfs("");

return res;

}



</details>

Copy link
Collaborator

@flydongwoo flydongwoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

으아 20주차 PR은 제가 제일 먼저 올리려고 벼르고 있었는데 선수 뺏겼네요 하하...

사용할 글자는 A, E, I, O, U 총 5개이고, 길이는 1~5이므로
어떤 자리 i(0부터)의 글자를 하나 올리면, 그 뒤에 올 수 있는 모든 조합 수만큼 “통째로 건너뛴다”고 볼 수 있어요.
이 '건너뛰는 묶음 크기'를 저는 자리별 가중치로 뒀습니다.

자리 i에서 뒤에 붙을 수 있는 길이는 0..(4-i)이고, 각 길이마다 경우의 수는 5^k개이므로
image
수치로는 W = [781, 156, 31, 6, 1]로 뒀습니다.

예를 들면, "AAAAE"라 가정했을 때

0번 자리 A: 0*781 + 1 = 1

1번 자리 A: 0*156 + 1 = 1 → 누적 2

2번 자리 A: 0*31 + 1 = 1 → 누적 3

3번 자리 A: 0*6 + 1 = 1 → 누적 4

4번 자리 E: 1*1 + 1 = 2 → 누적 6

이렇게 나옵니다.

#include <string>
#include <vector>

using namespace std;

int solution(string word) {
    int weight[5] = {781, 156, 31, 6, 1};
    auto val = [](char c) {
        if (c == 'A') {
            return 0;
        } else if (c == 'E') {
            return 1;
        } else if (c == 'I') {
            return 2;
        } else if (c == 'O') {
            return 3;
        } else {
            return 4;
        }
    };

    int answer = 0;
    for (int i = 0; i < (int)word.size(); i++) {
        answer += val(word[i]) * weight[i] + 1;
    }
    return answer;
}

문제 푸시느라 고생 많으셨어요! 방학 끝나기 전에 함 봅시다잉
그립습니다.

Copy link
Collaborator

@Seol-Munhyeok Seol-Munhyeok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itertools.product 사용 풀이

저는 문제를 보고 AEIOU 중 길이가 1~5인 중복순열의 개수를 생각해보고 그 값은 $5^1 + 5^2 + 5^3 + 5^4 + 5^5=3905$ 이므로 그냥 다 나열해서 풀어도 충분하겠다고 생각했습니다.
중복순열을 만들어주는 itertools 모듈의 product 함수를 사용해서 길이가 1부터 5인 문자열을 lst에 담고 이를 정렬한 후, 찾는 word의 인덱스를 반환하도록 했습니다.

from itertools import product
def solution(word):
    lst = []
    for length in range(1, 6):
        for p in product("AEIOU", repeat=length):
            lst.append(''.join(p))
    lst.sort()
    return lst.index(word) + 1

product 함수에 대해 간단히 설명하자면, 하나 이상의 iterable(리스트, 튜플, 문자열 등)을 인자로 받아서, 각 iterable에서 가능한 모든 조합의 데카르트 곱을 생성하는 함수입니다.

itertools.product(*iterables, repeat=1)

사용 예시는 다음과 같습니다.

print(list(product([1, 2], ['A', 'B'])))
# [(1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')]

print(list(product([1, 2], repeat=2)))
# [(1, 1), (1, 2), (2, 1), (2, 2)]

위 문제처럼 AEIOU 문자열 중에서 중복을 허용해서 length개를 뽑으려면 product("AEIOU", repeat=length) 처럼 repeat 인자임을 명확하게 적어야합니다.
단순히 product("AEIOU", length)로 작성하면 오류가 발생합니다.

DFS 풀이

그러나 좀 더 출제자의 의도에 부합하는 풀이는 DFS 풀이인 거 같습니다.
왜나면 DFS 탐색 순서가 정확히 사전 순서와 일치하기 때문입니다.
따로 sort 함수로 정렬을 하는 부분없이 좀 더 직관적인 풀이입니다.

vowels = ['A', 'E', 'I', 'O', 'U']
answer = 0
found = False

def dfs(current, word):
        global answer, found
        if found:
            return
        
        if current:
            answer += 1
            if current == word:
                found = True
                return
            
        if len(current) == 5:
            return

        for v in vowels:
            dfs(current + v, word)
            
def solution(word):
    dfs('', word)
    return answer

자리별 가중치 부여 방식

동우 님 풀이 방식인데 정말 생각도 하지못한 획기적인 방법이네요. 연산 횟수가 최대 5밖에 안되는 거의 $O(1)$ 풀이에 가깝네요... 만약 5개의 문자가 아니라 26개 문자 전체를 구하라. 이런 문제였다면 이 방법만 풀이가 가능할 거 같다는 생각이 듭니다. 좋은 관점 하나 얻어갑니다.


문제를 풀다보니 거의 탐색 문제는 순열, 조합, 중복순열, 중복조합, 모든 부분집합 찾기 내에서 다 풀리는 거 같습니다. 따라서 위 4가지 라이브러리 사용법과 혹시 모르니 직접 구현하는 방법도 익혀둬야 할 거 같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants