시저 암호

나의 기록·2026년 7월 13일

코딩테스트

목록 보기
26/35

문제

https://school.programmers.co.kr/learn/courses/30/lessons/12926

문자열 s의 각 알파벳을 n만큼 밀어서 암호문을 만드는 문제. 공백은 그대로 두고, z를 밀면 a로 순환한다는 조건이 포인트다.

  • s는 알파벳 대/소문자, 공백으로만 구성
  • n은 1~25

처음 짠 코드

일단 골격만 잡고 시작했다.

for(int i=0; i<s.length(); i++){
    int nowIdx=0;
    boolean capital = false;

    if(s.charAt(i)==" "){
        continue;
    }

    if(s.charAt(i)>=65 && s.charAt(i)<=90){
       nowIdx= s.charAt(i)-'A';
       capital =true;
    }else if(s.charAt(i)>=97 && s.charAt(i)<=122){
       nowIdx= s.charAt(i)-'a'; 
    }  
}

대/소문자 판별하고 기준점(nowIdx)까지는 구했는데, 정작 민 다음 실제로 밀어서 결과 문자열을 만드는 부분이 통째로 빠져 있었다. 여기서부터 하나씩 막힌 부분을 뚫어나갔다.

막혔던 부분들

1. 공백 비교에서 컴파일 에러

if(s.charAt(i)==" "){   // 에러

charAt()char를 반환하는데 " "String이라 비교가 안 됐다. char 리터럴은 작은따옴표(' ')로 써야 한다는 걸 놓치고 있었다.

2. 순환(wrap-around)을 특수 처리로 풀려고 했던 실수

z를 밀면 a가 된다는 조건을 보고, 처음엔 이걸 if문으로 따로 처리하려고 했다.

if(s.charAt(i)==90 && n>0){
    nowIdx=65;
}

근데 이렇게 하면 nowIdx(0~25 범위여야 하는 값)에 65가 들어가버려서 이후 계산이 다 꼬였다. 게다가 이 조건을 소문자 분기 안에도 그대로 복붙해놔서, 애초에 진입 자체가 불가능한 죽은 코드였다.

돌아보니 순환은 애초에 % 26 연산 하나로 다 해결되는 문제였다. nowIdx가 25(z)이고 n=1이면 (25+1)%26 = 0, 즉 자동으로 a의 인덱스로 돌아간다. 특수 처리가 필요 없었고, 오히려 버그의 원인이었다.

nextIdx = (nowIdx + n) % 26;

핵심 정리: 몇 바퀴를 돌든 상관없이, % 26이 "26칸짜리 순환에서 최종적으로 위치한 자리"를 그대로 계산해준다. 순환 조건을 보면 반사적으로 나머지 연산부터 떠올려야 한다.

3. int → char 형변환 문제

인덱스를 다시 문자로 바꾸는 부분에서 막혔다.

char chr = 'A' + nextIdx;   // 컴파일 에러

Java에서 charint를 연산하면 결과가 자동으로 int로 승급(promotion)된다. 그래서 'A' + nextIdxint이고, 이걸 char 변수에 그대로 넣으려면 "possible lossy conversion" 에러가 난다. 명시적 캐스팅이 필요했다.

char chr = (char)('A' + nextIdx);

Character.toString()을 쓰려고 했는데, 이것도 캐스팅된 char를 넘겨야 의도한 오버로드(Character.toString(char))로 들어간다. 사실 StringBuilderappend(char)를 바로 지원해서, 굳이 String으로 변환할 필요조차 없었다.

sb.append(chr);

4. 안 쓰는 변수 정리

디버깅하다 만든 int[] apb 배열과 System.out.println이 로직 어디에도 안 쓰여서 그대로 제거했다.

최종 코드

import java.util.Arrays;

class Solution {
    public String solution(String s, int n) {
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < s.length(); i++) {
            int nowIdx = 0;
            int nextIdx = 0;
            char chr = '0';
            boolean capital = false;

            if (s.charAt(i) == ' ') {
                sb.append(' ');
                continue;
            }

            if (s.charAt(i) >= 65 && s.charAt(i) <= 90) {
                nowIdx = s.charAt(i) - 'A';
                capital = true;
            } else if (s.charAt(i) >= 97 && s.charAt(i) <= 122) {
                nowIdx = s.charAt(i) - 'a';
            }

            nextIdx = (nowIdx + n) % 26;

            if (capital) {
                chr = (char) ('A' + nextIdx);
            } else {
                chr = (char) ('a' + nextIdx);
            }
            sb.append(chr);
        }

        return sb.toString();
    }
}

예제 3개 다 통과 확인 완료 ("AB",1→"BC", "z",1→"a", "a B z",4→"e F d").

배운 점

  • char 리터럴은 작은따옴표: charAt()의 반환 타입은 char. String(" ")과 비교하면 컴파일 자체가 안 된다.
  • 순환 조건 = 나머지 연산: "N개를 돌고 넘어가면 처음으로 돌아간다" 류의 조건을 보면 if로 예외 처리하기 전에 % 연산으로 풀 수 있는지부터 생각하기. 오히려 특수 처리가 버그를 만들었다.
  • char + int 연산은 int로 승급됨: char끼리, 혹은 charint를 연산하면 결과는 int. 다시 char에 담으려면 명시적 캐스팅 (char)가 필요하다.
  • StringBuilder.append(char): Character.toString()으로 문자열 변환 안 거치고 char를 바로 append할 수 있다.
  • 안 쓰는 변수/디버깅 코드는 제출 전에 정리: 삽질 과정에서 만든 배열, println은 로직 검증 후 지우기.
profile
뭐든 남겨본다

0개의 댓글