백준 철벽 보안 알고리즘

KIMYEONGJUN·2025년 9월 14일
0
post-thumbnail

문제

내가 생각했을때 문제에서 원하는부분

입력의 첫 줄에는 테스트 케이스의 수를 의미하는 하나의 정수가 입력된다.
정수는 100을 넘지 않는다.
각 테스트케이스마다 아래 항목들을 한 줄씩 입력받는다.
한 문장의 단어 수 n (1 ≤ n ≤ 1 000)
제 1 공개키
제 2 공개키
암호문
모든 단어들은 최소 1개, 최대 10개의 대문자들로 이루어져있다.

각 케이스마다
암호문을 해독한 평문
을 한 줄에 줄력한다.

내가 이 문제를 보고 생각해본 부분

초기 설정 및 입력 준비
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));:
BufferedReader는 대량의 입력을 효율적으로 읽을 때 사용되는 객체이다. 
InputStreamReader(System.in)는 표준 입력(키보드 등)을 문자로 읽을 수 있게 해주고, BufferedReader가 그 문자를 버퍼에 담아 한 줄씩 읽는 기능을 제공한다.
이 문제처럼 여러 줄의 입력이 있을 때 Scanner보다 빠르고 안정적이다.
StringBuilder sb = new StringBuilder();:
StringBuilder는 여러 개의 문자열을 효율적으로 합칠 때 사용한다. 
String 객체는 불변(immutable)이라서 문자열을 합칠 때마다 새로운 String 객체가 생성되어 메모리 낭비가 심하지만, StringBuilder는 가변(mutable)이라서 기존 객체에 계속 덧붙일 수 있어 성능상 유리하다.
최종 결과를 한 번에 출력하는 용도로 좋다.
int T = Integer.parseInt(br.readLine());:
입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 
br.readLine()으로 한 줄을 읽어 String으로 받은 후, Integer.parseInt()를 통해 정수형으로 변환하여 T 변수에 저장한다.
for (int t = 0; t < T; t++):
T개의 테스트 케이스를 각각 처리하기 위한 반복문이다.
각 테스트 케이스는 독립적으로 처리된다.
각 테스트 케이스별 입력 읽기
int n = Integer.parseInt(br.readLine());:
각 테스트 케이스마다 다음 줄에는 한 문장의 단어 수 n이 주어진다. 
n은 암호화될 문장의 단어 개수를 나타낸다.
String[] publicKey1 = new String[n];, String[] publicKey2 = new String[n];, String[] cipherText = new String[n];:
각각 제 1 공개키, 제 2 공개키, 암호문을 저장할 String 배열들을 n개의 공간만큼 미리 생성한다.
StringTokenizer st1 = new StringTokenizer(br.readLine());:
다음 세 줄은 각각 제 1 공개키, 제 2 공개키, 암호문이 공백으로 구분된 단어들로 주어진다.
StringTokenizer는 문자열을 특정 구분자(여기서는 공백)를 기준으로 쪼개어(토큰화하여) 하나씩 가져오는 데 사용된다.
br.readLine()으로 한 줄 전체를 읽은 후, StringTokenizer 객체를 생성하여 단어들을 분리할 준비를 한다.
for(int i = 0; i < n; i++) { publicKey1[i] = st1.nextToken(); }:
st1.nextToken()을 반복 호출하여 n개의 단어를 차례대로 publicKey1 배열에 저장한다. 
publicKey2와 cipherText도 동일한 방식으로 저장한다.
재배치 규칙 파악하기 (permutation 배열 생성)
HashMap<String, Integer> publicKey1Map = new HashMap<>();:
HashMap의 중요성: 이 부분이 매우 중요하다.
제 1 공개키(publicKey1)의 단어는 유니크(중복되지 않음)하다는 조건이 있다. 
우리는 '제 2 공개키의 특정 단어가 원래 제 1 공개키에서 몇 번째 위치에 있었는지'를 빠르게 찾아야 한다.
HashMap은 Key-Value 쌍으로 데이터를 저장하며, Key를 통해 Value를 평균 O(1)의 시간 복잡도로 조회할 수 있다.
여기서는 Key로 제 1 공개키의 단어를, Value로 그 단어의 원래 인덱스를 저장한다.
for(int i = 0; i < n; i++) { publicKey1Map.put(publicKey1[i], i); }:
publicKey1 배열을 순회하면서, publicKey1[i] (i번째 단어)를 Key로, i (그 단어의 인덱스)를 Value로 publicKey1Map에 저장한다.
이제 어떤 단어가 주어졌을 때, publicKey1Map.get(단어)를 호출하면 그 단어가 publicKey1에서 몇 번째에 있었는지 바로 알 수 있다.
int[] permutation = new int[n];:
permutation 배열: 이 배열은 제 2 공개키가 제 1 공개키로부터 어떻게 만들어졌는지 그 순서 변경 규칙을 담는다.
permutation[i] 값은 제 2 공개키의 i번째 단어가 원래 제 1 공개키의 permutation[i]번째 위치에 있던 단어를 의미한다.
for (int i = 0; i < n; i++) { permutation[i] = publicKey1Map.get(publicKey2[i]); }:
publicKey2 배열을 i번째 단어부터 순회한다.
publicKey2[i]는 제 2 공개키의 i번째 단어이다.
publicKey1Map.get(publicKey2[i])는 publicKey2[i]라는 단어가 제 1 공개키에서 몇 번째 인덱스에 있었는지를 찾아준다.
이 찾아낸 원래 인덱스 값을 permutation[i]에 저장합니다. 이렇게 permutation 배열이 완성된다.
평문 복구하기
String[] plainText = new String[n];:
최종적으로 복구된 평문 단어들을 저장할 String 배열이다.
for (int i = 0; i < n; i++) { plainText[permutation[i]] = cipherText[i]; }:
cipherText[i]는 현재 암호문의 i번째 단어이다.
문제에 따르면 암호문은 평문을 제 2 공개키를 만든 규칙의 반대로 재배치하여 만들어진다고 했다.
이것은 암호문의 i번째 단어가, 원래 평문의 permutation[i]번째 위치에 있던 단어라는 의미와 정확히 일치한다.
따라서 cipherText[i]를 plainText 배열의 permutation[i] 인덱스에 넣어주면 된다.
이 반복문이 완료되면 plainText 배열에는 복원된 평문이 올바른 순서대로 채워진다.
결과 출력
for (int i = 0; i < n; i++) { sb.append(plainText[i]); if (i < n - 1) { sb.append(" "); } }:
plainText 배열에 있는 복원된 단어들을 StringBuilder(sb)에 차례대로 추가한다.
단어들 사이에는 공백을 한 칸 넣어줍니다. (if (i < n - 1) { sb.append(" "); })
마지막 단어 뒤에는 불필요한 공백이 붙지 않도록 조건문을 사용한다.
sb.append("\n");:
각 테스트 케이스의 결과가 한 줄에 출력되어야 하므로, 하나의 테스트 케이스의 단어들을 모두 출력한 후에는 개행 문자(\n)를 추가한다.
System.out.print(sb.toString());:
모든 테스트 케이스의 처리가 끝난 후, StringBuilder에 모아둔 최종 결과를 한 번에 표준 출력(콘솔)으로 내보낸다. 
이렇게 하면 출력이 많을 때도 효율적이다.
br.close();:
BufferedReader 사용이 끝났으므로, 열려있던 스트림 자원을 닫아준다.

코드로 구현

package baekjoon.baekjoon_30;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.StringTokenizer;

// 백준 9322번 문제
public class Main1146 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder(); // 결과를 한 번에 출력하기 위한 StringBuilder

        int T = Integer.parseInt(br.readLine()); // 테스트 케이스의 수

        // 각 테스트 케이스를 처리합니다.
        for(int t = 0; t < T; t++) {
            int n = Integer.parseInt(br.readLine()); // 한 문장의 단어 수

            // --- 제 1 공개키, 제 2 공개키, 암호문을 한 줄씩 읽어 배열에 저장합니다. ---
            String[] publicKey1 = new String[n];
            String[] publicKey2 = new String[n];
            String[] cipherText = new String[n];

            StringTokenizer st1 = new StringTokenizer(br.readLine()); // 제 1 공개키 읽기
            for(int i = 0; i < n; i++) {
                publicKey1[i] = st1.nextToken();
            }

            StringTokenizer st2 = new StringTokenizer(br.readLine()); // 제 2 공개키 읽기
            for(int i = 0; i < n; i++) {
                publicKey2[i] = st2.nextToken();
            }

            StringTokenizer st3 = new StringTokenizer(br.readLine()); // 암호문 읽기
            for(int i = 0; i < n; i++) {
                cipherText[i] = st3.nextToken();
            }

            // --- 재배치 규칙 파악하기 ---
            // 1. 제 1 공개키의 단어와 그 원래 인덱스를 빠르게 찾을 수 있도록 HashMap에 저장합니다.
            //    (단어 -> 인덱스) 매핑
            HashMap<String, Integer> publicKey1Map = new HashMap<>();
            for(int i = 0; i < n; i++) {
                publicKey1Map.put(publicKey1[i], i);
            }

            // 2. 'permutation' 배열을 만들어 제 2 공개키의 각 단어가 제 1 공개키의 어디에 있었는지를 기록합니다.
            //    permutation[i] = "제 2 공개키의 i번째 단어는 제 1 공개키의 permutation[i]번째 단어였다"
            int[] permutation = new int[n];
            for(int i = 0; i < n; i++) {
                // 제 2 공개키의 i번째 단어를 가져와, 이 단어가 제 1 공개키에서 몇 번째 인덱스였는지 찾습니다.
                permutation[i] = publicKey1Map.get(publicKey2[i]);
            }

            // --- 평문 복구하기 ---
            // 복구된 평문을 저장할 배열을 생성합니다.
            String[] plainText = new String[n];

            // 암호문의 각 단어를 평문의 올바른 위치에 넣어줍니다.
            // 핵심: 암호문의 'i'번째 단어(cipherText[i])는 원래 평문의 'permutation[i]'번째 자리에 위치해야 합니다.
            for(int i = 0; i < n; i++) {
                plainText[permutation[i]] = cipherText[i];
            }

            // --- 결과 출력 ---
            for(int i = 0; i < n; i++) {
                sb.append(plainText[i]);
                if(i < n - 1) { // 마지막 단어가 아니면 단어 사이에 공백을 추가합니다.
                    sb.append(" ");
                }
            }
            sb.append("\n"); // 각 테스트 케이스의 결과를 한 줄에 출력한 후 개행합니다.
        }
        System.out.print(sb.toString()); // 모든 테스트 케이스의 최종 결과를 한 번에 출력합니다.
        br.close(); // BufferedReader 리소스를 해제합니다.
    }
}

마무리

코드와 설명이 부족할수 있습니다. 코드를 보시고 문제가 있거나 코드 개선이 필요한 부분이 있다면 댓글로 말해주시면 감사한 마음으로 참고해 코드를 수정 하겠습니다.

profile
Junior backend developer

0개의 댓글