17회차 코딩테스트 기록

2026-09-21 · JAVA 2문제 + SQL 2문제 · 3 PASS · 1 FAIL

제출 코드는 고치지 않고 그대로 두고, FAIL 한 문제만 원인별로 나눠 정리했다. 아래 “로컬 검증”은 이 저장소에서 직접 돌려 본 결과이며 프로그래머스 채점 화면이 아니다.

17회차 · JAVA

자릿수 더하기 ✅ PASS

10으로 나눈 나머지가 지금의 1의 자리, 10으로 나누면 그 자리를 버린다.

문제 원문 · 제출 코드

JAVA · 제출 코드 그대로
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

접근은 맞았다. 고정 크기 배열을 중복 제거 장치로 쓰려다 크기·빈칸·유효 길이 세 곳에서 어긋났다.

문제 원문 · 제출 코드 · 정정본 · TreeSet본

JAVA · 제출 코드 그대로
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] 존재표로 따로 생성
17회차 · SQL

이름이 없는 동물의 아이디 ✅ PASS

NULL은 값이 아니라 “값이 없음”이라 = 로 비교할 수 없다.

문제 원문 · 제출 코드

SQL · 제출 코드 그대로
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건이라는 것도
같은 스크립트에서 함께 확인했다.
17회차 · SQL

있었는데요 없었습니다 ✅ PASS

두 테이블에 모두 있는 개체만 비교하면 되므로 INNER JOIN이면 충분하다.

문제 원문 · 제출 코드 · 검증 스크립트

SQL · 제출 코드 그대로
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). 픽스처의 입양일 순서가 보호 시작일 순서와 우연히 같아서, "별칭을 빼먹으면 순서가 달라진다"는 주장을 보여 주지 못하는 검사였다. 값을 잡아 두 정렬 결과가 실제로 갈라지게 고쳤다. 검사가 통과했다는 것과 검사가 의미 있다는 것은 다르다.

이 회차에서 남길 것