public class Solution {
public int solution(int n) {
int answer = 0;
while (n > 0) {
answer += n % 10;
n /= 10;
}
return answer;
}
}
정수 나눗셈이 소수점을 버리는 성질을 그대로 쓴 풀이라 문자열로 바꿀 필요가 없다. n은 문제에서 1 이상이므로 while (n > 0)으로 충분하다. 다만 매개변수 n을 직접 깎아 쓰고 있다는 점은 의식해 두는 게 좋다 — 원본 값이 뒤에서 또 필요한 코드였다면 따로 담아 두어야 한다.
로컬 검증
1~1,000,000 전수 + 상한 부근(9,999,999 · 10,000,000)
기댓값은 문자열로 자릿수를 더하는 다른 방법으로 따로 만들어 비교
17회차 · JAVA
두 개 뽑아서 더하기 ❌ FAIL
접근은 맞았다. 고정 크기 배열을 중복 제거 장치로 쓰려다 크기·빈칸·유효 길이 세 곳에서 어긋났다.
public boolean same(int num, int[] answer){
for(int i=0;i<answer.length;i++){ // ← 빈칸까지 훑는다
if(num == answer[i]){ return false; }
}
return true;
}
public int[] solution(int[] numbers) {
int[] temp = new int[100]; // ← 최대 201가지를 담을 수 없다
int count = 0;
for(int i=0;i<numbers.length-1;i++){
for(int j=i+1;j<numbers.length;j++){
int num = numbers[i]+numbers[j];
if(same(num,temp)==true){
temp[count] = numbers[i]+numbers[j];
count++;
}else{ continue; }
}
}
Arrays.sort(temp); // ← 빈칸 0이 전부 앞으로 몰린다
int[] answer = new int[count];
int count2=0;
for(int i=0; i<temp.length; i++){
if(temp[i]!=0){ // ← 정답인 0까지 버린다
answer[count2]=temp[i];
count2++;
}
}
return answer;
}
① 배열 크기가 모자랐다 — 런타임 에러
원소가 0 이상 100 이하이므로 두 수의 합은 0~200, 즉 최대 201가지다. new int[100]은 이걸 담지 못한다.
② 중복 검사가 빈칸까지 훑었다 — 합이 0이면 사라진다
int 배열은 생성과 동시에 전부 0으로 채워진다. 제출한 same은 answer.length 전체를 돌기 때문에, 합이 0일 때 아직 쓰지 않은 뒤쪽 칸의 0과 마주쳐 “이미 있는 값”으로 착각하고 건너뛴다. 실제로 채운 개수인 count까지만 검사하면 해결된다.
③ 0을 빈칸으로 오해하고 지웠다
배열 전체를 Arrays.sort하면 남아 있던 빈칸 0들이 전부 앞으로 몰린다. 그 뒤 “0이 아닌 값만 골라 담기”로 빈칸을 걸러 내려 했는데, 이 방식은 정답인 0까지 같이 버린다. Arrays.copyOf(temp, count)로 유효 구간만 잘라낸 뒤 정렬하면 빈칸이 애초에 섞이지 않는다.
JAVA · 같은 요구사항을 컬렉션으로
TreeSet<Integer> sums = new TreeSet<>();
for (int i = 0; i < numbers.length - 1; i++)
for (int j = i + 1; j < numbers.length; j++)
sums.add(numbers[i] + numbers[j]);
return sums.stream().mapToInt(Integer::intValue).toArray();
중복 제거 + 오름차순 정렬은 TreeSet의 기본 동작이라 위 세 함정이 처음부터 생기지 않는다. 배열 풀이를 알아 두는 것은 “자료구조가 대신해 주던 일이 무엇이었나”를 알기 위해서다 — 크기 산정·중복 검사·유효 길이 관리가 전부 직접 해야 할 일이었다는 것이 위 세 실수로 드러난다.
로컬 검증 — 제출본의 실패를 실제로 재현함
[0,0,1] 기대 [0, 1] / 제출본 [1]
0~99 (길이 100) 고유한 합 197개
제출본 → ArrayIndexOutOfBoundsException: Index 100 out of bounds for length 100
정정본·TreeSet본: 공식 예제 2건 / 원소 0~3·길이 2~5 조합 전수 1,360건
/ 제한 범위 안 무작위 5,000건 / 전부 0·전부 100·길이 상한 경계
기댓값은 TreeSet도 배열 누적도 아닌 boolean[201] 존재표로 따로 생성
SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NULL
ORDER BY ANIMAL_ID ASC;
NULL은 어떤 값과도 같다고도 다르다고도 판정되지 않는다. 그래서 NAME = NULL은 에러가 아니라 0건이다 — 조용히 빈 결과가 나오는 쪽이라 오히려 위험하다. 반드시 IS NULL · IS NOT NULL을 쓴다.
또 빈 문자열 ''은 NULL이 아니다. 이름 칸을 비워 저장한 데이터와 아예 이름이 없는 데이터를 같이 묶고 싶다면 조건을 따로 써 줘야 한다.
실행 결과제출한 쿼리를 임시 테이블 픽스처에 그대로 돌렸다 (MariaDB 12.3)
픽스처: A100(NULL) · A200('체리') · A300(NULL) · A400('') · A500('보리')
--- 이름이 없는 동물의 아이디 (기대: A100, A300) ---
+-----------+
| ANIMAL_ID |
+-----------+
| A100 |
| A300 |
+-----------+
빈 문자열을 넣어 둔 A400 은 결과에 없다 — '' 는 "길이가 0인 값"이지
"값이 없음"이 아니기 때문이다. NAME = NULL 이 에러 없이 0건이라는 것도
같은 스크립트에서 함께 확인했다.
SELECT I.ANIMAL_ID, I.NAME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE O.DATETIME < I.DATETIME
ORDER BY I.DATETIME ASC;
INNER JOIN — 보호 기록과 입양 기록이 둘 다 있는 개체만 비교 대상이다. LEFT JOIN을 써도 짝 없는 행의 NULL이 WHERE에서 어차피 탈락해 결과는 같지만, 불필요한 행을 만들었다가 버리는 셈이다.
날짜 비교 — DATETIME은 과거가 작은 값이다. “입양일이 보호 시작일보다 앞선다”가 곧 O.DATETIME < I.DATETIME이다.
정렬 — “보호 시작일이 빠른 순”이므로 I.DATETIME 오름차순. ASC는 기본값이지만 의도를 드러내려고 적었다.
실행 결과제출한 쿼리를 임시 테이블 픽스처에 그대로 돌렸다 (MariaDB 12.3)
--- 있었는데요 없었습니다 (기대: A200 체리, A500 보리) ---
+-----------+--------+
| ANIMAL_ID | NAME |
+-----------+--------+
| A200 | 체리 |
| A500 | 보리 |
+-----------+--------+
--- 항목별 확인 (passed = 1 이면 통과) ---
① IS NULL 이 이름 없는 2건만 고른다 passed = 1
② = NULL 은 0건이다 (에러가 아니라 빈 결과) passed = 1
③ 빈 문자열 '' 은 IS NULL 에 걸리지 않는다 passed = 1
④ INNER JOIN 이 짝 없는 A100·A300 을 버린다 passed = 1
⑤ LEFT JOIN 으로 바꿔도 결과가 같다 passed = 1
⑥ 뒤집힌 기록 A200,A500 만 골라낸다 passed = 1
⑦ I.DATETIME 오름차순이면 A200 이 먼저다 passed = 1
⑧ O.DATETIME 으로 정렬하면 A500 이 먼저다 passed = 1
TEMPORARY 테이블만 사용 — 같은 이름의 실제 테이블이 있어도 가려지기만 하고
바뀌지 않으며 세션이 끝나면 사라진다. 수업 데이터·문제 원본은 건드리지 않았다.
⑧은 처음에 실패했다(passed = 0). 픽스처의 입양일 순서가 보호 시작일 순서와 우연히 같아서, "별칭을 빼먹으면 순서가 달라진다"는 주장을 보여 주지 못하는 검사였다. 값을 잡아 두 정렬 결과가 실제로 갈라지게 고쳤다. 검사가 통과했다는 것과 검사가 의미 있다는 것은 다르다.
이 회차에서 남길 것
고정 크기 배열을 중복 제거에 쓰면 크기·빈칸·유효 길이 세 가지를 전부 직접 관리해야 한다. 하나라도 빠지면 조용히 틀린다.
int[]의 기본값 0은 “아직 안 씀”과 “값이 0”을 구분하지 못한다. 그래서 count를 따로 들고 다녀야 했다.
SQL의 NULL도 같은 종류의 함정이다 — “값이 없음”을 값처럼 비교하면 에러 없이 0건이 나온다.
둘 다 “비어 있음을 무엇으로 표현할 것인가” 하나의 주제다. 이번 회차에서 자바와 SQL이 같은 질문을 서로 다른 얼굴로 냈다.