Even Idiots Can Make Game
백준 1213 S3 팰린드롬 만들기
- Date/Lastmod
- Section
- PS
- Title
- 백준 1213 S3 팰린드롬 만들기
- Ps-Tags
- Solved
- 03-06/25
#1 접근
임한수와 임문빈씨는 완전히 정신나간 커플이어서 이런 말도안되는 선물을 주고받나 보다.
아무튼, 입력으로 들어오는 문자열의 최대 길이 50에 비해 시간제한 2초는 매우 넉넉하므로, 최적화를 빡빡하게 하는 문제가 아니라 정말로 팰린드롬을 어떻게 만들 것인가에 대한 지저분한 구현 문제처럼 접근하면 된다.
#1 주어진 문자열을 팰린드롬으로 만드는 법
문자열을 구성하는 문자의 갯수를 모두 세 보자.
입력은 어차피 알파벳 대문자로만 들어오므로 {'A': 2, 'B': 3}
과 같은 해시 테이블에 기록할 수 있을 것이다.
만일 모든 문자가 짝수번 등장하면, 팰린드롬을 만들 수 있다.
문자열 구성하는 모든 문자 $c_i$가 $n_i$번 등장한다면, 이 문자는 팰린드롬의 앞 쪽 절반에 $\frac{n_i}{2}$번, 뒤 쪽 절반에 $\frac{n_i}{2}$번 등장하면 되기 때문이다.
홀수 번 등장하는 문자가 단 하나 존재하면, 팰린드롬을 만들 수 있다.
팰린드롬이 문자 $c_i$를 중심으로 앞뒤로 대칭이라고 생각하자. 문자 $c_i$는 팰린드롬의 전반부, 후반부를 합해 일단 짝수 번 등장하고, 중앙에 한 번 등장하므로 $c_i$의 갯수 $n_i$는 홀수이다.
홀수 번 등장하는 문자가 하나를 초과하여 존재하면, 팰린드롬을 만들 수 없다.
#1 알고리즘
- 입력된 문자열의 모든 문자의 빈도를 기록하는 해시 테이블을 생성한다.
- 홀수 번 등장하는 문자가 존재하지 않거나 단 하나 존재하는지 확인한다.
- 하나를 초과하여 등장한다면, 팰린드롬을 만들 수 없는 것이므로 빠르게 종료한다.
- 홀수 번 등장하는 문자가 단 하나 존재한다면, 해시 테이블에서 그 문자의 빈도를 짝수로 만들고, 그 문자를 기억해 둔다.
- 기억된 문자는 해시 테이블의 중앙에 배치될 것이다.
- 해시 테이블을 이용해 팰린드롬의 전반부를 만든다.
- 만일 홀수 번 등장하는 문자가 단 하나 존재했다면, 기억해둔 그 문자를 생성하는 팰린드롬에 붙인다.
- 팰린드롬의 전반부를 뒤집어 붙여서 나머지 팰린드롬을 완성한다.
#1 코드
|
|
#1 기타
#2 문자열
- 사실
std::string
은std::basic_string<char>
의 타입 별칭std::basic_string<T>
에서T
에 무엇이 오냐에 따라 타입 별칭이 다 달라짐 (다양한 문자 타입으로 이루어진 문자열을 지원하기 위한 구조)
- 문자열 리터럴 뒤에
s
를 붙이면std::string
리터럴을 만들 수 있음.- 예를 들어, “hello"의 타입은
const char[]
임."hello"s
의 타입은std::string
임."hello"
의 경우 프로그램의 텍스트 영역에 저장되므로 수정이 불가능한 고정된 문자열이나,"hello"s
는 수정이 가능한 가변적인 문자열.
- 예를 들어, “hello"의 타입은
#2 문자열 붙이기
C++ std::string
의 append
메서드에는 질릴 정도로 많은 바리에이션이 있다.
원형을 다 적고 분석하는건 의미가 없을 것 같고 다음을 보자.
string a = "abc";
// 문자를 여러개 붙이기
cout << "he"s.append(2, 'l') << endl; // hell
// 문자열을 붙이기
cout << "str"s.append("ring") << endl; // string
// 붙일 문자열의 특정 인덱스부터 갯수만큼 짤라 붙이기
cout << "str"s.append("foo", 1, 2) << endl; // stroo
// 붙일 문자열의 시작부터 지정한 갯수만큼 짤라 붙이기
cout << "str"s.append("foobar", 3) << endl; // strfoo
// 반복자로 가져다 붙이기
cout << "str"s.append(a.begin(), a.end()) << endl; // strabc
// 초기화 리스트로 가져다 붙이기
cout << "str"s. append({'a', 'b', 'c'}) << endl; // strabc
#2
std::map
과 std::unordered_map
둘 다 연관 컨테이너이며, 키-값 쌍을 저장하고 관리하는 데에 사용된다.
키를 저장하는 방식에 있어서 약간 다른 점이 있고 이 점이 성능적인 차이로도 연결되기 때문에 해결하려는 문제에 맞는 컨테이너를 사용하는 것이 중요하다.
간단히 얘기하면 std::map
은 키를 항상 정렬된 상태로 유지하며, std::unordered_map
은 이름이 암시하는 것처럼 키의 정렬을 보장하지 않는다.
이 문제에서는 팰린드롬이 여러 개 만들어질 수 잇는 경우 사전순으로 앞선 것을 출력하라고 하였다. 때문에 문자의 빈도를 기록할 때 문자의 사전순을 유지하는 것이 중요했으므로 std::map
을 사용했다.
std::unordered_map
은 키의 정렬을 보장하지 않는 대신에 삽입, 삭제, 검색 연산의 평균적인 시간복잡도가 $O(1)$이다. 1 이는, 사실상 std::unordered_map
의 내부 구현이 키의 해시 값을 인덱스로 하는 배열 접근과 유사하기 때문에 그렇다.
반면, std::map
은 정렬된 상태를 유지해야 하기 때문에 삽입, 삭제, 겁색 연산의 평균적인 시간 복잡도가 $O(\log{n})$이다. 내부적으로는 레드 블랙 트리를 이용하여 구현한다.
#1 참고 문헌
해시 충돌이 발생하는 경우 $O(N)$까지 늘어날 수는 있다. ↩︎