문제를 읽은 순간 아스키 코드를 활용해야겠다는 생각이 들긴 했다.
근데, string을 char로 변환하고, 공백 처리, z->a로 변환할 엄두가 안 났다. switch문을 이용하기에는 너무 노가다....
생각이 구체화가 안되어서 결국 외부 찬스.......
접근은 맞았는데 가장 뒤 문자에서 현재 문자를 빼 범위를 한정시킬 생각을 못 했다.
아직 이런 아이디어적인 부분이 부족하다.
일단 오늘 내용 정리!
n만큼 오른쪽으로 밀어 새로운 문자로 변환.z → a, Z → A).mod 연산 활용 (O(N))class Solution {
public String solution(String s, int n) {
StringBuilder answer = new StringBuilder();
for (char c : s.toCharArray()) {
if (Character.isUpperCase(c)) {
answer.append((char) ((c - 'A' + n) % 26 + 'A'));
} else if (Character.isLowerCase(c)) {
answer.append((char) ((c - 'a' + n) % 26 + 'a'));
} else {
answer.append(c); // 공백 유지
}
}
return answer.toString();
}
}
이 코드에서는 대문자 (A-Z)와 소문자 (a-z)의 아스키 코드 범위를 직접 비교하여 문자 변환을 수행.
이를 이해하려면 아스키 코드(ASCII) 값과 mod 연산에 대한 개념이 필요.
| 문자 유형 | 아스키 코드 범위 | 예제 |
|---|---|---|
| 대문자 (A-Z) | 65 ~ 90 | 'A' = 65, 'Z' = 90 |
| 소문자 (a-z) | 97 ~ 122 | 'a' = 97, 'z' = 122 |
| 공백 (' ') | 32 | ' ' = 32 |
이 정보를 활용하여, char 값을 if 문을 사용해 알파벳 범위 내에서 처리.
if (c >= 'A' && c <= 'Z') {
answer.append((char) ((c - 'A' + n) % 26 + 'A'));
}
작동 원리
1. 현재 문자를 A 기준으로 0부터 시작하도록 조정
'A' = 65, 'Z' = 90 c - 'A' → 'A' - 'A' = 0, 'B' - 'A' = 1, ..., 'Z' - 'A' = 25n만큼 이동 후, mod 26 적용
(c - 'A' + n) % 26 'Y' (89) → (89 - 65 + 3) % 26 = (24 + 3) % 26 = 1 → 'B' (66)다시 'A'를 더해 원래 문자 범위로 복원
+ 'A' 1 + 'A' = 66 → 'B'if (c >= 'a' && c <= 'z') {
answer.append((char) ((c - 'a' + n) % 26 + 'a'));
}
작동 원리
c - 'a' → 소문자를 0부터 시작하는 인덱스로 변환.n만큼 이동 후 mod 26을 적용하여 범위 내 유지.'a'를 더해 문자 복원.💡 핵심 개념:
'A', 'a'를 기준으로 0부터 시작하도록 조정.n만큼 이동 후 mod 26을 적용하여 알파벳 범위를 유지.'A', 'a'를 더해 원래 문자로 복원.mod 26을 이용하여 문자 범위 벗어남 방지.StringBuilder를 사용하여 성능 최적화.O(N)).Map<Character, Character>를 사용한 문자 매핑 (O(N))import java.util.*;
class Solution {
public String solution(String s, int n) {
Map<Character, Character> upperMap = new HashMap<>();
Map<Character, Character> lowerMap = new HashMap<>();
for (char c = 'A'; c <= 'Z'; c++) {
upperMap.put(c, (char) ((c - 'A' + n) % 26 + 'A'));
}
for (char c = 'a'; c <= 'z'; c++) {
lowerMap.put(c, (char) ((c - 'a' + n) % 26 + 'a'));
}
StringBuilder answer = new StringBuilder();
for (char c : s.toCharArray()) {
if (upperMap.containsKey(c)) {
answer.append(upperMap.get(c));
} else if (lowerMap.containsKey(c)) {
answer.append(lowerMap.get(c));
} else {
answer.append(c); // 공백 유지
}
}
return answer.toString();
}
}
O(1) → O(N)보다 빠를 가능성 있음.HashMap 사용).Stream API 활용 (람다식)import java.util.stream.Collectors;
class Solution {
public String solution(String s, int n) {
return s.chars()
.mapToObj(c -> (char) (Character.isUpperCase(c) ? (c - 'A' + n) % 26 + 'A' :
Character.isLowerCase(c) ? (c - 'a' + n) % 26 + 'a' : c))
.map(String::valueOf)
.collect(Collectors.joining());
}
}
chars() → mapToObj() → collect()의 스트림 처리 방식 활용.O(N) 유지).String.replaceAll() + 정규표현식class Solution {
public String solution(String s, int n) {
return s.replaceAll("[A-Z]", m -> String.valueOf((char) ((m.group().charAt(0) - 'A' + n) % 26 + 'A')))
.replaceAll("[a-z]", m -> String.valueOf((char) ((m.group().charAt(0) - 'a' + n) % 26 + 'a')));
}
}
replaceAll()과 람다식을 이용하여 한 번의 replaceAll()으로 변환 가능.| 방법 | 시간 복잡도 | 장점 | 단점 |
|---|---|---|---|
| 방법 1 (mod 연산 활용) | O(N) | 가장 빠름, 직관적 | 없음 |
| 방법 2 (해시맵 매핑) | O(N) (조회 O(1)) | 빠른 조회 가능 | 메모리 사용량 증가 |
방법 3 (Stream API) | O(N) | 함수형 스타일로 깔끔 | 성능이 미세하게 떨어질 수 있음 |
방법 4 (replaceAll() + 정규식) | O(N) | replaceAll() 한 번으로 처리 | 정규식 사용으로 가독성 감소 |
mod 26을 활용한 기본 연산 (방법 1)O(N)으로 가장 빠르게 실행됨.StringBuilder 사용으로 메모리 절약.O(1)이므로, 대량의 변환이 필요할 때 유리 (방법 2).Stream API를 활용하면 코드가 간결해짐 (방법 3).replaceAll())을 사용하면 유지보수성이 높아질 수 있음 (방법 4).✅ 문제를 해결하는 방법은 여러 가지가 있으며, 상황에 따라 최적의 방법이 다를 수 있다.
✅ 기본적인 mod 연산을 활용하면 빠르고 간단하게 해결할 수 있다.
✅ Stream API, replaceAll() 등 다양한 접근 방식이 존재하며, 유지보수성과 성능을 고려해야 한다.
✅ 해시맵을 활용하면 조회 속도가 빨라질 수 있지만, 메모리 사용량이 증가할 수 있다.
📌 "알고리즘을 풀 때, 다양한 방법을 고민하는 습관을 기르자!" 🚀