프로그래머스 12926 - 시저 암호 (Lv.1)
핵심 접근 — 대소문자별 기준점을 빼 0..25 상대 좌표로 옮기고 % 26
프로그래머스 12926 - 시저 암호 (Lv.1)
출처: https://school.programmers.co.kr/learn/courses/30/lessons/12926
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 프로그래머스 12926 - 시저 암호 (Lv.1)
// https://school.programmers.co.kr/learn/courses/30/lessons/12926
// 문제 설명
// 문장의 각 알파벳을 n만큼 뒤로 밀어 암호화한다. "AB"를 1만큼 밀면 "BC",
// "z"를 1만큼 밀면 "a"가 된다. 문자열 s와 거리 n이 주어질 때 암호문을 반환하라.
// 제약 조건
// 공백은 밀지 않고 그대로 둔다. s는 소문자, 대문자, 공백으로만 구성된다.
// s의 길이 <= 8,000, n은 1 이상 25 이하의 자연수 (대문자는 대문자로, 소문자는 소문자로)
// Example
// Input : s = "AB", n = 1 Output: "BC"
// Input : s = "z", n = 1 Output: "a"
// Input : s = "a B z", n = 4 Output: "e F d"
// 접근 — 대소문자별 기준점을 빼서 0..25 상대 위치로 바꾸고 % 26
// 'z' + 1이 '{'가 되는 것을 막으려면 알파벳 구간을 0부터 시작하는 좌표로 옮겨야 한다.
// 1) 대문자면 기준점 'A', 소문자면 'a'를 잡는다.
// 2) c - base로 0..25 상대 위치를 얻고 n을 더한 뒤 % 26으로 감아준다.
// 3) 다시 base를 더해 원래 구간(대문자/소문자)으로 되돌린다 — 두 구간을 한 식으로 처리.
// 공백은 어느 구간에도 없으므로 건너뛴다.
// 시간 O(n), 공간 O(1) (입력 문자열을 제자리 변환)
#include <string>
#include <cctype>
using namespace std;
string solution(string s, int n) {
for (char& c : s) { // 참조로 받아 제자리 변환
if (c == ' ') continue; // 공백은 밀지 않는다
char base = isupper(static_cast<unsigned char>(c)) ? 'A' : 'a'; // 구간 기준점
c = base + (c - base + n) % 26; // 0..25로 옮겨 밀고 감은 뒤 복귀
}
return s;
}
정리
- 이 문제의 전부는
'z' + 1이'a'가 아니라'{'라는 사실이다. ASCII에서 소문자 구간은 97~122로 끝나고 그 뒤에{가 붙어 있어서, 그냥 더하면 알파벳 밖으로 튀어나간다. 감아주려면 나머지 연산이 필요하고, 나머지 연산은 0에서 시작하는 좌표계에서만 의미가 맞는다. - 그래서
base를 빼는 것이 필수 단계다.c - base로 0..25 상대 위치를 얻고 →+ n→% 26→+ base로 복귀.(c + n) % 26처럼 base를 빼지 않고 나머지를 취하면 122가 아니라 아무 값이나 나온다('z'면 122 % 26 = 18 ='\x12'). 좌표 이동 → 연산 → 역이동이 한 세트다. - 대문자와 소문자를 두 갈래로 복사해 쓰지 않는 이유도 여기 있다. 차이는
base값 하나뿐이라 기준점만 변수로 뽑으면 동일한 한 식으로 두 구간을 처리한다.if (isupper) {...} else {...}로 갈라 쓰면 같은 산술이 두 번 나오고, 한쪽만 고치는 실수가 생긴다. - 공백은
continue로 건너뛴다. 공백(32)은 두 알파벳 구간 어디에도 없으므로, 밀어버리면 엉뚱한 기호가 되고isupper가 false라 소문자 기준점이 잡혀 더 어긋난다. “바꾸지 않을 문자”를 먼저 걸러내는 가드가 대소문자 판정보다 앞에 와야 하는 순서. - 제약이
1 <= n <= 25라(c - base + n)이 항상 음수가 아니고 최대 50이라% 26한 번으로 충분하다. 만약 왼쪽으로 미는(복호화) 변형이면 음수 나머지가 구현체 정의로 음수가 되므로((x % 26) + 26) % 26형태가 필요해진다. 시저 암호를 일반화할 때 첫 번째로 깨지는 지점. - 길이 8,000에 O(n) 한 번 순회,
for (char& c : s)로 제자리 변환이라 공간 O(1). 12930 이상한 문자 만들기와 골격이 같은 계열이지만 — 12930은 위치의 홀짝으로 대소문자를 바꾸고 공백을 카운터 리셋 신호로 쓰는 반면, 여기서는 값을 모듈러로 밀고 공백은 그냥 건너뛴다.c - 'a'인덱스 변환이 룩업(5622 다이얼)·집합 비트(1987 알파벳)에 이어 좌표 이동으로 쓰이는 세 번째 용도. - 검증: 예제 3개(“BC”, “a”, “e F d”)와 랩어라운드 경계(
"Zz Aa", n=25 →"Yy Zz") 통과 (MSVC/std:c++17컴파일·실행).
핵심 요약 —
'z' + 1은'a'가 아니라'{'이므로, 대소문자별 기준점을 빼서 0..25 상대 좌표로 옮긴 뒤% 26으로 감고 다시 기준점을 더한다. base를 뽑아내면 대문자·소문자를 한 식으로 처리하고, 공백 가드를 대소문자 판정보다 먼저 둔다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.