알고리즘/문제 풀이

5052번: 전화번호 목록

Themion 2021. 12. 22. 17:12

https://www.acmicpc.net/problem/5052

 

5052번: 전화번호 목록

첫째 줄에 테스트 케이스의 개수 t가 주어진다. (1 ≤ t ≤ 50) 각 테스트 케이스의 첫째 줄에는 전화번호의 수 n이 주어진다. (1 ≤ n ≤ 10000) 다음 n개의 줄에는 목록에 포함되어 있는 전화번호가

www.acmicpc.net

각 문자열의 부분 문자열이 중복되는지를 찾는 문제이므로 trie를 사용할 수도 있고, 부분 문자열을 일일이 map에 저장해 풀 수도 있다.

- trie (포인터 기반)

트라이에 문자열을 저장하면서 기존에 입력받은 문자열을 발견할 경우, 긴급전화가 겹치므로 해당 테스트 케이스는 일관성이 없는 목록이라고 할 수 있다.

#include <iostream>

using namespace std;

// trie 클래스
class node {
public:
    // 현재 노드가 리프 노드라면 true
    bool is_leaf = false;
    // 뒤에 올 수 있는 문자 '0' ~ '9'
    node* next[10];

    // 모든 자식 노드를 NULL로 초기화
    node() { for (int i = 0; i < 10; i++) next[i] = NULL; }
    // c에 해당하는 자식을 반환
    node* push (char c) {
        int i = c - '0';
        // 해당 자식이 NULL이라면 생성
        if (next[i] == NULL) next[i] = new node();
        return next[i];
    }
};

// 각 테스트 케이스를 입력받아 실행
bool test_case() {
    // 주어진 경우가 가능하다면 true, 아니라면 false
    bool ans = true;
    // 문자열을 한 글자씩 입력받기 위한 버퍼
    char buf;
    // 테스트 케이스에서 주어지는 문자열의 개수
    int n;
    // trie: 트라이를 저장할 공간, ptr: trie를 탐색하기 위한 포인터
    node trie, *ptr;

    cin >> n;
    cin.get();

    // 테스트 케이스의 각 문자열에 대해
    while (n--) {
        ptr = &trie;

        // while문을 통해 문자열을 한 글자씩 탐색
        while ((buf = cin.get()) != '\n') {
            // 입력받은 문자를 trie에 push한 뒤
            ptr = ptr->push(buf);
            // 지금까지 입력받은 부분 문자열이 이전에 입력받은 전체 문자열일 때
            // 현재 테스트 케이스는 불가능한 경우이다
            if (ptr->is_leaf) ans = false;
        }
        
        // 현재 문자열이 이전에 입력받은 문자열의 부분 문자열이라면
        // ptr->next 중 NULL이 아닌 값이 존재하므로 이를 확인하면
        // 현재 테스트 케이스는 불가능한 경우이다
        for (int i = 0; i < 10; i++) if (ptr->next[i] != NULL) ans = false;
        // 문자열 입력을 마쳤으므로 현재 문자열의 끝을 표시
        ptr->is_leaf = true;
    }

    // 현재 테스트 케이스가 가능한 경우인지를 출력
    return ans;
}

int main() {
    int t;
    // 테스트 케이스의 수를 입력받은 뒤
    cin >> t;
    // 각 테스트 케이스가 일관성 있는 테스트 케이스인지 여부를 반환
    while (t--) printf("%s\n", test_case() ? "YES" : "NO");

    return 0;
}

 

- trie (인덱스 기반)

#include <iostream>

using namespace std;

#define MAX_N 10000
#define MAX_LEN 10
#define ALP 10

// 트라이의 각 노드
struct node {
    // child[i]: i에 해당하는 자식이 존재한다면 그 자식의 인덱스, 아니라면 0
    int child[ALP] = { 0, };
    // 현재 노드가 리프 노드라면 true, 아니라면 false
    bool is_leaf = false;
};

bool test_case() {
    // 주어진 테스트 케이스가 일관성이 있는 목록이라면 true, 아니라면 false
    bool ret = true;
    // 문자열을 한 글자씩 입력받을 공간
    char c;
    // n: 전화번호의 개수
    // i: 트라이에 접근하기 위한 인덱스, len: 트라이에 존재하는 노드 수
    int n, i, len = 0;
    // trie[i]: i번째로 push된 노드의 상태
    node trie[MAX_N * MAX_LEN];

    // 전화번호의 개수를 입력받은 뒤 개행 문자를 입력받아 에러 방지
    cin >> n;
    cin.get();

    // 각 전화번호에 대해
    while (n--) {
        // 루트 노드에서부터 차례로 push
        i = 0;
        // 전화번호의 각 글자에 대해
        while ((c = cin.get()) != '\n') {
            // 현재 테스트 케이스가 일관성이 없다면 continue
            if (!ret) continue;

            // 현재 글자에 해당하는 자식 노드가 없다면 자식 노드를 새로 생성
            if (!trie[i].child[c - '0']) trie[i].child[c - '0'] = ++len;
            // 현재 글자에 해당하는 자식 노드로 이동한 뒤
            i = trie[i].child[c - '0'];
            // 이전에 현재 전화번호의 부분 문자열이 입력된 적 있다면
            // 현재 테스트 케이스는 일관성이 없음
            ret &= !(trie[i].is_leaf);
        }
        // 전화번호 입력을 마쳤으므로 리프 노드를 설정
        trie[i].is_leaf = true;
        // 현재 노드에 자식 노드가 존재하면
        // 현재 전화번호를 부분 문자열로 삼는 전화번호가 존재하므로
        // 현재 테스트 케이스는 일관성이 없음
        for (int j = 0; j < ALP; j++) ret &= !(trie[i].child[j]);
    }

    // 입력받은 테스트 케이스가 일관성 있는 목록인지 여부를 반환
    return ret;
}

int main() {
    // 입출력 속도 향상
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int t;
    // 테스트 케이스의 수를 입력받은 뒤
    // 각 테스트 케이스가 일관성 있는 목록인지 여부를 출력
    for (cin >> t; t--; ) cout << (test_case() ? "YES" : "NO") << '\n';

    return 0;
}

 

- map

각 문자열을 배열과 map에 각각 저장한 뒤, 각 문자열의 부분 문자열이 map에 존재한다면 긴급전화가 겹치므로 해당 테스트 케이스는 일관성이 없는 목록이라고 할 수 있다.

#include <iostream>
#include <set>
#include <string>

using namespace std;

#define MAX_N 10000

// 각 테스트 케이스를 입력받아 실행
bool test_case() {
    bool ans = true;
    char buf;
    int n;
    set<string> s;
    string str[MAX_N];
    
    // 문자열의 수를 입력받은 뒤
    cin >> n;
    // 각 문자열을 배열과 map에 각각 저장
    for (int i = 0; i < n; i++) {
        cin >> str[i];
        s.insert(str[i]);
    }

    // 각 부분 문자열이 map에 존재하는지 확인
    for (int i = 0; i < n; i++) {
        // substr(i, j)의 시간 복잡도는 O(i + j)이므로
        // 빠른 연산을 위해 새 변수를 만들어 부분 문자열을 차례로 저장
        string lookup = "";
        for (int j = 0; j < str[i].size() - 1; j++) {
            lookup += str[i][j];
            ans &= s.find(lookup) == s.end();
        }
    }
    return ans;
}

int main() {
    int t;
    // 테스트 케이스의 수를 입력받은 뒤
    cin >> t;
    // 각 테스트 케이스가 일관성 있는 테스트 케이스인지 여부를 반환
    while (t--) printf("%s\n", test_case() ? "YES" : "NO");

    return 0;
}