Algorithm

[알고리즘] 둘만의 암호

montmer27 2026. 4. 24. 00:21

Before

import java.util.*;

class Solution {
    public String solution(String s, String skip, int index) {
        String answer = "";
        // skip의 각 문자를 set에 저장 -> c 와 c+index 사이의 알파벳이 skip에 포함되었는지 빠르게 확인하기 위함
        // 스트림보다는 문자 배열로 변환한 뒤 각 배열의 원소를 추가하는 방식이 오버헤드가 덜함
        Set<Character> skipSet = new HashSet<>();
        for (char c : skip.toCharArray()) {
            skipSet.add(c);
        }
        // StringBuilder 생성 -> 문자 개별 변환 뒤 추가
        StringBuilder answerStringBuilder = new StringBuilder();
        for(int i = 0; i < s.length(); i++) {
            int rotation = rotateWithSkip(s.charAt(i), skipSet, index);
            char charAfterRotation = cyclicShift(s.charAt(i) + rotation);
            answerStringBuilder.append(charAfterRotation);
        }
        // StringBuilder -> String
        answer = answerStringBuilder.toString();
        return answer;
    }

    // 문자가 skipSet에 있는 문자들을 제외하고 총 몇 칸을 이동해야 하는지 계산하여 반환하는 메서드
    public int rotateWithSkip(char c, Set<Character> skipSet, int index) {
        int i = 0;
        int rotation = 1;
        while(rotation <= index) {
            if(skipSet.contains(cyclicShift(c + rotation))) {
                i++;
                c++;
            }
            rotation++;
        }
        return rotation + i - 1;
    }

    // 숫자를 넣으면 알파벳으로 변환하되, 소문자 범위를 넘어서면 다시 'a'부터 시작하는 변환기
    public char cyclicShift(int c) {
        return (char) ((c-'a') % 26 + 'a');
    }
}

코드를 분석했습니다. 버그를 찾았어요! rotateWithSkip 메서드에 두 가지 핵심 버그가 있습니다.

  

 🐛 버그 1: c++ 위치 문제 (가장 치명적)

    // 현재 코드 (잘못됨)
    if(skipSet.contains(cyclicShift(c + rotation))) {
        i++;
        c++;  // ← c를 증가시키면서 rotation은 그대로 두면
    }         //   다음 루프에서 c+rotation이 두 칸 앞을 가리키게 됨
    rotation++;


    

c를 직접 바꿔버리면 다음 rotation 증가 시 실제로 두 칸을 건너뜁니다. c는 원래 문자 그대로 두고, skip을 만났을 때는 index를 1 늘려서 더 멀리 가도록 해야 합니다.

  

 🐛 버그 2: return rotation + i - 1

rotation과 i를 따로 관리하다 보니 반환값 계산도 복잡해지고 틀리기 쉽습니다.

  

 ✅ 수정된 rotateWithSkip

 public int rotateWithSkip(char c, Set<Character> skipSet, int index) {
        int count = 0;   // skip을 제외하고 실제로 센 칸 수
        int rotation = 0; // c에서 실제로 이동한 칸 수 (skip 포함)
        
        while (count < index) {
            rotation++;
            char next = cyclicShift(c + rotation);
            if (!skipSet.contains(next)) {
                count++;  // skip 문자가 아닐 때만 카운트
            }
        }
        return rotation;
    }



핵심 아이디어: c는 건드리지 않고, rotation을 1씩 늘려가면서 skip이 아닌 문자를 만날 때만 count를 올립니다. count == index가 되면 그때의 rotation이 실제 이동 거리입니다.


이 수정으로 "aukks", "wbqd", 5 → "happy" 예제도 정확히 통과하고 나머지 실패 케이스들도 해결될 거예요!