Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6-YIM2UL2ET #21

Merged
merged 2 commits into from
Mar 4, 2024
Merged

6-YIM2UL2ET #21

merged 2 commits into from
Mar 4, 2024

Conversation

YIM2UL2ET
Copy link
Collaborator

@YIM2UL2ET YIM2UL2ET commented Feb 27, 2024

🔗 문제 링크

BOJ 1599 - 민식어

✔️ 소요된 시간

30m

✨ 수도 코드

1. 문제 설명

알파벳의 문자 체계를 변형한 "민식어" 라는 문자 체계가 있습니다.
"민식어"의 글자 순서는 a b k d e g h i l m n ng o p r s t u w y 순으로 구성되어 있습니다.
이중에서 ng 는 n과 o 사이의 완전히 새로운 문자입니다.

이 문제는 "민식어"로 구성된 단어를 입력 받아 사전 순으로 출력하는 문제입니다.

2. 문제 해석 및 풀이

1. 정렬 방식

보자마자 정렬을 이용하는 문제라는 것은 다들 아시겠죠?

우리가 일반적인 알파벳 문자 체계의 단어를 정렬해서 출력한다면 C++에서는 다음과 같이 쉽게 코드를 짤 수 있습니다.

int main(void)
{
    int n;
    std::cin >> n;

    std::string str[n];
    for (int i = 0; i < n; i++) std::cin >> str[i];
    
    std::sort(str, str+n);

    for (int i = 0; i < n; i++) std::cout << str[i] << std::endl;
    return 0;
}

하지만 이 문제는 알파벳 문자 체계를 약간 변형한 민식어 문자 체계를 따르는 단어를 정렬해야 하기 때문에 정렬을 할 때 정렬 방식을 정해주어야 합니다.

정렬 방식은 두 가지가 있습니다.

  1. 민식어 단어를 알파벳의 순서를 따르도록 일종의 번형을 하여 만들어진 새 알파벳 단어의 정렬 기준에 따라 정렬한다.
  2. 민식어 단어를 알파벳의 단어 정렬 기준을 따른 민식어 문자 체계의 정렬 기준을 만들어 해당 기준에 따라 정렬한다..

저는 1번 방식을 택했습니다.
이를 위해서는 민식어 단어를 알파벳 단어로 번역하는 translation 함수를 만들어야 합니다.

std::string translation(std::string min_str)
{
    std::string alpha_str = min_str;
    for (int i = 0; i < min_str.size(); i++) {
        // 기준을 토대로 한 글자 씩 번역
    }
    return alpha_str;
}

이제 이 반복문을 토대로 한글자씩 문자를 바꾸어 알파벳 단어로 바꾸어 봅시다.

2. 민식어 단어를 알파벳 단어로 번형 (1)

"민식어"의 글자 순서는 알파벳의 순서를 약간 변형해서 따르고 있기에 우선 이를 나열해서 비교해봅시다.

KakaoTalk_20240228_162333982

알파벳과 민식어의 차이는 다음을 제외하면 존재하지 않습니다.

  1. 알파벳 몇 가지는 민식어의 문자 체계에 포함되어 있지 않다.
  2. 알파벳 c의 자리에 k가 들어 가 있다.
  3. 알파벳에는 없는 민식어의 문자인 'ng'는 n과 o 사이에 위치한다.

1번은 전혀 문제가 될 것이 없습니다.
알파벳 몇 글자가 존재하지 않더라도 민식어의 글자 순서는 'k'와 'ng'를 제외하면 전부 알파벳의 순서를 따르기 때문입니다.
따라서 민식어의 'k'와 'ng'를 제외한 모든 문자들은 알파벳으로 번역 시 문자를 바꾸지 않아도 상관이 없습니다.

2번은 단순하게 민식어에서 'k'를 'c'로 바꾸면 됩니다.
'k'를 'c'로 바꾸면 알파벳의 순서를 따르기 때문에 알파벳의 정렬 기준에 맞춰집니다.
(민식어에는 'c'라는 문자가 존재하지 않으므로 민식어의 'k'를 'c'로 바꾸어도 무방합니다.)

// for문 안쪽에 추가
if (str[i] == 'k') new_str[i] = 'c';

3. 민식어 단어를 알파벳 단어로 번형 (2)

문제는 3번입니다. 3번이 문제가 되는 이유는 크게 두 가지 있습니다.

  1. 'ng'는 일반 민식어 글자와 다르게 알파벳 두 글자로 구성되어 있다.
  2. 알파벳 체계에서 'n'과 'o' 사이에 오는 문자가 존재하지 않는다.

이를 염두 해두면서 해결 방법을 찾아봅시다.

우선 'ng'라는 글자로 구성된 단어의 입력을 받아야 합니다.
민식어 단어에 구성된 글자를 하나씩 비교하므로
i번째 글자에 'n'이 나오면 그 뒤에 'g'가 나오는지 조건문을 통해서 구별하면 될 것입니다.

// for문 안쪽에 추가
else if (str[i] == 'n' && i+1 < str.size() && str[i+1] == 'g') {
    // 이 조건문 안에 들어온다면 min_str[i] ~ min_str[i+1]은 'ng'라는 글자로 구성되어 있는 것.
}

그 후엔 이 'ng'라는 글자를 이 위치에 오는 알파벳으로 바꾸어 주면 될 것 처럼 보입니다.
하지만 'ng'는 알파벳에서는 존재하지 않는 'n'과 'o' 사이에 오는 글자이기 때문에 그럴 수 없습니다.

그렇다면 민식어 글자에서 'ng' 이후에 오는 글자들을 알파벳 문자로 변형할 때 한 글자씩 옮겨주도록 합시다.
민식어 문자 체계에서 맨 끝 글자는 'y'이기 때문에 한 글자씩 옮겨주어도 알파벳의 마지막 글자인 'z'를 넘지 않으므로 가능한 방법입니다.

KakaoTalk_20240228_162333982_01

'ng' 뒤에 있는 'o' ~ 'y'까지 한 글자씩 옮겨주면 'ng'의 자리에 'o'가 들어올 수 있는 것을 확인할 수 있습니다.
이는 'n'보다 큰 문자에 증가연산 ++를 해주는 것으로 해결할 수 있습니다.

// for문 안쪽에 추가
else if (str[i] > 'n') new_str[i]++;

이제 민식어 단어에서 'ng'가 나온다면 'o'로 변경해주면 되겠지요.
'ng'는 알파벳 두 글자로 되어 있으므로 우선 i번째 글자를 지워주고, i+1번째 글자로 이동해봅시다.
그 후 i+1번째 글자를 o로 변형해주면 될 것입니다.

else if (str[i] == 'n' && i+1 < str.size() && str[i+1] == 'g') {
    new_str.erase(new_str.begin() + (i++));
    new_str[i] = 'o';
}
// but, 이 코드는 잘못된 코드입니다.

이렇게 되면 참 좋겠지만 아직 문제가 남아있습니다.
왜냐하면 'ng'를 'o'로 바꾸게 되면 알파벳 단어는 민식어 단어보다 사이즈가 줄어듭니다.
'ng'는 알파벳 두 글자로 구성되어 있기 때문이죠.

따라서 위 코드는 우리가 원하는 대로 작동하지 않고 다음과 같이 작동합니다.
KakaoTalk_20240228_162333982_02

이를 해결하기 위해서 s_diff라는 변수로 alpha_str의 인덱스 차이를 조절해주어야 합니다.
s_diff를 처음에 0으로 초기화 해주고, 'ng'라는 글자가 나왔을 때 차이가 1만큼 벌어지므로 ++s_diff 해주면 될 것 입니다.

else if (str[i] == 'n' && i+1 < str.size() && str[i+1] == 'g') {
    new_str.erase(new_str.begin() + (i++) - s_diff);
    new_str[i - (++s_diff)] = 'o';
}

또한 'ng'가 한번 나오면 이를 'o'라는 글자로 수정하고 나면 크기 차이가 계속 진행되므로
함수 내에 있는alpha_str[i]를 일괄적으로 alpha_str[i-s_diff]로 변경해줍니다.

따라서 함수의 최종 코드는 다음과 같습니다.
std::string translation(std::string min_str)
{
int s_diff = 0; // alpha_str과 min_str의 크기 차이
std::string alpha_str = min_str;
for (int i = 0; i < min_str.size(); i++) {
if (min_str[i] == 'k') alpha_str[i-s_diff] = 'c';
else if (min_str[i] > 'n') alpha_str[i-s_diff]++;
else if (min_str[i] == 'n' && i+1 < min_str.size() && min_str[i+1] == 'g') {
alpha_str.erase(alpha_str.begin() + (i++) - s_diff);
alpha_str[i - (++s_diff)] = 'o';
}
}
return alpha_str;
}

3. 최종 코드

#include <iostream>
#include <algorithm>

std::string translation(std::string min_str)
{
    int s_diff = 0;
    std::string alpha_str = min_str;
    for (int i = 0; i < min_str.size(); i++) {
        if (min_str[i] == 'k') alpha_str[i-s_diff] = 'c';
        else if (min_str[i] > 'n') alpha_str[i-s_diff]++;
        else if (min_str[i] == 'n' && i+1 < min_str.size() && min_str[i+1] == 'g') {
            alpha_str.erase(alpha_str.begin() + (i++) - s_diff);
            alpha_str[i - (++s_diff)] = 'o';
        }
    }
    return alpha_str;
}

bool compare(std::string str1, std::string str2)
{
    return translation(str1) < translation(str2);
}

int main(void)
{
    int n;
    std::cin >> n;

    std::string min_str[n];
    for (int i = 0; i < n; i++) std::cin >> min_str[i];
    
    std::sort(min_str, min_str+n, compare);

    for (int i = 0; i < n; i++) std::cout << min_str[i] << std::endl;
    return 0;
}

string 클래스를 활용하면 부등호를 활용하여 문자열의 사전순 비교가 가능합니다.

📚 새롭게 알게된 내용

PR이 늦어졌네요. 죄송합니다..

오늘은 조금 단순한 문제를 들고 왔습니다.
원래 연결리스트를 공부해서 관련 문제를 풀어보려고 했는데
뭔가 지난번 문제들 성격이랑 너무 비슷해서 아예 다른 문제를 선택해서 왔네요.
다음 PR은 순환 알고리즘을 공부하여 풀어오겠습니다!

Copy link
Member

@kjs254 kjs254 left a comment

Choose a reason for hiding this comment

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

k 케이스와 ng 케이스를 각각 구분 지어서 깔끔하게 접근하시고

알파벳을 나열하셔서 설명해주신 덕분에 한번에 이해가 됐습니다.

문자를 치환 후 단순히 정렬하는 간단한 문제라 생각했는데 ng 케이스가 복병이었네요

특히 s_diff를 이용하여 인덱스 차이를 줄이는 것이 인상깊었습니다.

Copy link
Collaborator

@rivkms rivkms left a comment

Choose a reason for hiding this comment

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

휼륭한 풀이입니다.

처음에 접근할 때에는 글자와 숫자를 연결시켜 민식어에 맞는 정렬 기준을 만든 후 해야하나 생각을 했지만 이 방법을 알게되고 되게 신기했습니다.
특히 ng에 대하여 고민을 하시고 한칸씩 밀겠다고 결정한 것이 매우 훌륭했다고 생각합니다.

생각 못했던 방법이라 생각의 방법이 늘어난 것 같습니다. 나중에 다시 한번 풀어봐야겠네요
수고하셨습니다😁

Copy link
Collaborator

@mong3125 mong3125 left a comment

Choose a reason for hiding this comment

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

쉬운 문제라고 하셨는데 쉽게 푸셔서 쉬운 문제라고 느끼신듯 합니다. 대단하시네요..

문제를 보자마자 생각난 방법은 문자 하나하나 마다 숫자를 부여해서 비교하는 방법이었습니다. 사전순과 거의 비슷해서 꼭 그럴필요가 없다는건 설명을 보고 알았네요.

index가 차이나서 s_diff를 사용하는 방법이 인상적이었던것 같습니다.

@YIM2UL2ET YIM2UL2ET merged commit 27d5b9f into main Mar 4, 2024
@YIM2UL2ET YIM2UL2ET deleted the 6-YIM2UL2ET branch March 4, 2024 12:36
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.

4 participants