← Back to list

[CT스터디] 코딩테스트 입문·실전 문제를 풀며 조건 해석과 탐색 기준을 재정의한 문제 해결 기록

Problem-Solving Notes on Grid Interpretation, Greedy Intervals, Candidate Search, and Boundary Conditions in Python

software engineer · 2026-06-29 11:04 · 0 claps · 30.4 min read
#python #coding-test #greedy-algorithms #combination #problem-solving
Open on Medium ↗
Wiki topics: 💻 · Programming

[CT스터디] 코딩테스트 입문·실전 문제를 풀며 조건 해석과 탐색 기준을 재정의한 문제 해결 기록

Problem-Solving Notes on Grid Interpretation, Greedy Intervals, Candidate Search, and Boundary Conditions in Python

1. 짧은 도입부

이번 실습은 단순히 여러 개의 코딩테스트 문제를 제출한 기록이 아니라, 문제 설명을 코드로 바꾸는 과정에서 반복적으로 막혔던 지점을 해결한 기록이다. 카펫, 점프와 순간 이동, 캐릭터의 좌표, 멀쩡한 사각형, 비밀 코드 해독, 요격 시스템까지 총 6개 문제를 풀면서 공통적으로 확인한 문제는 “문제의 문장을 어떻게 변수와 조건문으로 바꿀 것인가”였다.

처음에는 문제마다 요구사항이 달라 보였지만, 실제로는 비슷한 패턴이 반복되었다. 전체 경우를 만들고 조건으로 걸러야 하는 문제, 경계값을 정확히 해석해야 하는 문제, 수학 공식을 적용해야 하는 문제, 정렬 기준을 잘 잡아야 하는 문제가 섞여 있었다. 특히 내가 어려워했던 부분은 코드 문법 자체보다도 “왜 이 변수를 만들어야 하는지”, “왜 이 조건이 들어가야 하는지”, “문제에서 말한 값이 실제로 어떤 의미인지”를 이해하는 과정이었다.

2. 핵심 작업 요약

이번 실습에서 해결한 문제는 총 6개다.

첫 번째는 카펫 문제였다. 갈색 격자와 노란색 격자의 개수를 바탕으로 전체 카펫의 가로와 세로를 구하는 문제였다. 여기서는 “가로 길이”와 “세로 길이”가 실제 길이가 아니라 격자의 칸 수라는 점을 이해하는 것이 중요했다. 노란색 영역을 먼저 정하고, 그 바깥에 갈색 테두리가 한 칸씩 둘러싸인다는 구조로 바꾸면서 문제를 풀 수 있었다.

두 번째는 점프와 순간 이동 문제였다. 앞으로 이동하는 과정을 직접 시뮬레이션하려고 하면 경우의 수가 복잡해진다. 대신 목표 지점 n에서 거꾸로 돌아오면서, 짝수일 때는 순간이동의 역연산으로 나누고 홀수일 때는 점프가 필요했다고 판단하는 방식으로 해결했다.

세 번째는 캐릭터의 좌표 문제였다. 방향 입력에 따라 좌표를 이동시키되, 보드의 경계 밖으로 나가지 않아야 했다. 여기서는 x, y 좌표의 의미와 up, down의 방향 조건이 헷갈릴 수 있었다. 보드의 절반 크기를 map_x, map_y로 정의하고, 이동 전에 경계를 검사하도록 구성했다.

네 번째는 멀쩡한 사각형 문제였다. 전체 격자 수에서 대각선이 지나가며 사용할 수 없게 되는 칸을 빼는 문제였다. 이 문제는 단순 시뮬레이션보다 공식 적용이 핵심이었다. 특히 w + h - gcd(w, h)라는 식이 왜 등장하는지 이해하기 어려웠고, 최종적으로는 “대각선 문제에서 못 쓰는 칸을 구하는 공식”으로 정리했다.

다섯 번째는 비밀 코드 해독 문제였다. 가능한 비밀 코드 후보를 모두 만들고, 각 후보가 주어진 질문 q와 응답 ans를 모두 만족하는지 검사해야 했다. 여기서는 q가 무엇인지, ans를 새로 만드는 것인지 이미 주어진 조건으로 비교하는 것인지가 핵심 혼동 지점이었다. combinations로 후보를 만들고, 후보와 질문의 일치 개수를 세어 비교하는 방식으로 해결했다.

여섯 번째는 요격 시스템 문제였다. 여러 개의 미사일 구간을 최소한의 요격 미사일로 모두 처리해야 하는 문제였다. 여기서는 구간이 닫힌 구간이 아니라 개구간 (s, e)이라는 점이 중요했다. 따라서 이전 발사 기준점과 현재 미사일의 시작점이 같아도 맞출 수 없고, 조건은 start >= shoot이 되어야 했다. 또한 가장 빨리 끝나는 구간부터 처리하기 위해 끝점 기준 정렬을 사용했다.

3. 문제 인식

이번 실습에서 가장 먼저 인식한 문제는, 코드를 작성하기 전에 문제 설명의 단어를 정확히 해석하지 않으면 코드가 쉽게 엉뚱한 방향으로 간다는 점이었다. 예를 들어 카펫 문제에서는 가로와 세로를 “길이”로 이해하면 감이 잘 오지 않았지만, 이를 “격자의 칸 수”로 바꾸어 이해하자 노란색 내부 영역과 갈색 테두리의 관계가 보이기 시작했다. 캐릭터 좌표 문제에서도 upy를 증가시키고 downy를 감소시킨다는 좌표 규칙을 정리하기 전까지는 조건을 반대로 작성할 위험이 있었다.

두 번째로 인식한 문제는, 많은 문제에서 직접적인 구현보다 “기준을 어떻게 잡을 것인가”가 더 중요하다는 점이었다. 점프와 순간 이동 문제는 앞으로 점프와 순간이동을 조합하려고 하면 복잡하지만, 목표 지점에서 거꾸로 내려오면 짝수와 홀수만 보면 된다. 요격 시스템도 모든 구간의 겹침을 일일이 비교하려고 하면 복잡하지만, 끝나는 지점 기준으로 정렬하면 한 번 쏜 위치로 처리 가능한 구간과 새로 쏴야 하는 구간을 쉽게 구분할 수 있다.

이번 실습에서 내가 실제로 어려워한 지점은 “코드가 왜 이렇게 되어야 하는지”였다. 예를 들어 candidate, question, expected_count, match_count 같은 변수는 결과만 보면 단순해 보이지만, 각각이 어떤 문제 문장을 코드로 옮긴 것인지 이해하지 못하면 코드가 외워야 하는 덩어리처럼 느껴진다. 따라서 이번 기록에서는 단순히 정답 코드를 나열하는 것이 아니라, 각 코드가 어떤 문제를 해결하기 위해 필요했는지를 중심으로 정리했다.

4. 문제 정의

이번 실습의 문제는 “6개의 코딩테스트 문제를 푸는 것”으로만 정의할 수 없다. 더 정확한 문제는, 서로 다른 유형의 문제에서 공통적으로 반복되는 해석 과정을 코드 구조로 바꾸는 것이었다. 카펫과 멀쩡한 사각형은 격자 기반 문제였고, 점프와 순간 이동은 수학적 관찰을 활용한 역방향 계산 문제였다. 비밀 코드 해독은 가능한 후보를 만들고 조건으로 필터링하는 완전탐색 문제였고, 요격 시스템은 정렬 기준을 세운 뒤 최소 개수를 구하는 그리디 문제였다.

또한 각 문제는 단순히 “for문을 쓰는가”, “if문을 쓰는가”의 문제가 아니었다. 카펫에서는 노란색 영역을 기준으로 전체 카펫 크기를 복원해야 했고, 비밀 코드 해독에서는 ans를 만드는 것이 아니라 ans와 비교해야 했다. 요격 시스템에서는 shoot = end가 실제로 end에 쐈다는 뜻이 아니라, end 바로 왼쪽에 쐈다고 간주하기 위한 기준값이라는 점을 이해해야 했다.

따라서 이번 문제 해결의 핵심 목표는 세 가지로 정리할 수 있다. 첫째, 문제 설명의 문장을 변수로 바꾸는 것. 둘째, 조건문이 필요한 이유를 문제의 제약에서 찾는 것. 셋째, 제출 후 정답 처리를 통해 구현이 문제의 요구사항을 실제로 만족하는지 검증하는 것이다. 이 기준으로 6개 문제를 다시 보면, 각 문제는 서로 다른 알고리즘이지만 모두 “문장 → 조건 → 변수 → 검증”의 흐름을 가진다.

5. 왜 이것을 문제로 인식했는가

코딩테스트 풀이에서 가장 위험한 상황은 코드가 돌아가긴 하지만, 내가 왜 그렇게 썼는지 설명할 수 없는 경우다. 예를 들어 targets.sort(key=lambda target: target[1])는 문법적으로는 짧은 한 줄이지만, 왜 끝점 기준으로 정렬해야 하는지 모르면 문제의 핵심을 잡은 것이 아니다. 마찬가지로 if start >= shoot도 단순 비교식이 아니라, 문제에서 구간을 (s, e)로 정의했기 때문에 “같아도 맞출 수 없다”는 제약을 반영한 조건이다.

또한 일부 문제는 예시 입력에서는 우연히 맞는 코드가 나올 수 있었다. 멀쩡한 사각형 문제에서 w * 2로 못 쓰는 칸을 계산하면 w=8, h=12 예시에서는 우연히 16이 나온다. 하지만 이는 일반 공식이 아니기 때문에 다른 입력에서는 틀린다. 이 경험은 예시 하나에 맞는 코드와 모든 테스트 케이스에 맞는 코드를 구분해야 한다는 점을 보여준다.

문제를 문제로 인식한 이유는 결국 “정답 제출”이 아니라 “재현 가능한 풀이 구조”가 필요했기 때문이다. 같은 유형의 문제가 다시 나왔을 때, 풀이를 외운 것이 아니라 조건을 다시 세울 수 있어야 한다. 이번 실습에서는 이 점을 기준으로 각 문제에서 막혔던 부분을 다시 정의하고, 코드가 어떤 판단 과정을 수행하는지 설명 가능한 형태로 정리했다.

6. 문제 해결 경험 1 — 카펫: 격자 구조를 노란색 내부 영역 기준으로 다시 정의하기

카펫 문제의 첫 번째 어려움은 brown, yellow가 각각 갈색 칸과 노란색 칸의 개수라는 점은 이해했지만, 전체 가로와 세로를 어떻게 역산할지 바로 떠오르지 않았다는 점이었다. 처음에는 전체 크기를 기준으로 가능한 경우를 찾으려는 접근이 자연스럽게 보였지만, 그렇게 하면 갈색 테두리와 노란색 내부 영역의 관계가 명확하지 않았다.

원인은 문제의 구조를 전체 카펫에서 바로 보려고 했기 때문이다. 이 문제는 전체 카펫을 먼저 찾는 것보다, 노란색 영역을 먼저 정하는 편이 더 쉽다. 노란색 영역은 반드시 직사각형이고, 그 바깥을 갈색 테두리가 한 칸씩 감싼다. 따라서 노란색의 세로 길이를 하나씩 가정하고, 나누어떨어질 때 노란색 가로 길이를 구한 뒤, 양쪽 테두리 때문에 전체 가로와 세로에 각각 2를 더하면 된다.

조치는 yellow_height를 기준으로 반복문을 돌리는 방식으로 구현했다. yellow % yellow_height == 0이면 해당 세로 길이로 노란색 직사각형을 만들 수 있고, yellow_width = yellow // yellow_height로 가로 길이를 계산할 수 있다. 이후 전체 크기는 brown_width = yellow_width + 2, brown_height = yellow_height + 2로 복원했다. 마지막으로 전체 넓이 brown_width * brown_heightbrown + yellow와 같은지 검증했다.

# yellow_width = 노란색이 가로로 몇 칸
# yellow_height = 노란색이 세로로 몇칸
def solution(brown, yellow):
    total = brown + yellow
    # 노란색 세로를 잡아보기.
    for yellow_height in range(1, yellow +1):
        # yellow = 노란색 가로 * 노란색 세로
        if yellow % yellow_height == 0:
            yellow_width = yellow // yellow_height
            brown_width = yellow_width + 2
            brown_height = yellow_height +2
            if brown_width >= brown_height and brown_width  * brown_height == total:
                return [brown_width, brown_height]
print(solution(10, 2))   # [4, 3]
print(solution(8, 1))    # [3, 3]
print(solution(24, 24))  # [8, 6]

이 풀이의 확인 결과는 세 가지 기본 예시에서 모두 기대 결과를 반환하는 방식으로 검증했다. 특히 brown_width >= brown_height 조건을 통해 문제에서 요구하는 가로가 세로보다 같거나 길다는 조건도 함께 반영했다. 이 문제를 통해 격자 문제에서는 전체를 바로 찾기보다 내부 구조나 반복 가능한 작은 단위를 기준으로 문제를 재정의하는 것이 더 안정적이라는 점을 확인했다.

이미지 1 — 카펫: 노란색 내부 영역을 기준으로 전체 카펫 크기를 복원하고 정답 제출을 완료한 검증 화면

이미지 1 — 카펫: 노란색 내부 영역을 기준으로 전체 카펫 크기를 복원하고 정답 제출을 완료한 검증 화면

7. 문제 해결 경험 2 — 점프와 순간 이동: 앞으로 가는 문제를 뒤에서 되돌아오는 문제로 바꾸기

점프와 순간 이동 문제에서는 처음에 “어떻게 앞으로 이동해야 배터리를 최소로 쓰는가”를 직접 생각하면 경우가 많아진다. 한 번에 몇 칸 점프할지, 언제 순간이동할지 판단해야 하기 때문이다. 이 방식은 작은 입력에서는 생각할 수 있지만, 입력 범위가 커지면 모든 이동 경로를 따지는 방식은 비효율적이다.

문제의 원인은 방향을 앞으로만 보고 있었다는 점이다. 이 문제는 앞으로 갈 때보다 목표 지점 n에서 거꾸로 돌아오는 방식이 훨씬 단순하다. 순간이동은 현재 거리의 두 배로 이동하는 것이므로, 거꾸로 보면 짝수 위치는 2로 나눌 수 있다. 이때 배터리는 들지 않는다. 반대로 n이 홀수라면 바로 절반으로 나눌 수 없으므로, 마지막에 1칸 점프가 필요했던 것으로 보고 배터리를 1 증가시킨 뒤 n에서 1을 뺀다.

조치는 while n > 0 반복문 안에서 짝수와 홀수를 나누어 처리하는 방식으로 구현했다. 짝수일 때는 n = n // 2, 홀수일 때는 battery += 1n -= 1을 수행한다. 이 과정은 목표 지점에서 0까지 되돌아가며 필요한 점프 횟수만 세는 구조다.

#한번에 k칸 앞으로 점프, (현재까지 온 거리)x2에 해당하는 위치로 순간이동 가능
# 순간이동 말고 점프 시 건전지 사용량이 듦.
# 거리가 n만큼 떨어져 있는 장소
# 건전지 사용량 최소
# n이 짝수면: 순간이동으로 온 거니까 2로 나눈다. 배터리 사용 없음.
# n이 홀수면: 순간이동으로 바로 올 수 없음. 1칸 점프가 필요했던 거니까 배터리 +1. n에서 1을 뺀다.
def solution(n):
    battery = 0
    while n>0:
        if n % 2 == 0:
            n = n//2
        else:
            battery += 1
            n -= 1
    return battery
print(solution(5))     # 2
print(solution(6))     # 2
print(solution(5000))  # 5

확인 결과 5, 6, 5000에 대해 각각 2, 2, 5가 출력되었다. 이 문제에서는 앞으로 이동하는 모든 가능성을 탐색하지 않고, 역방향으로 문제를 단순화하는 것이 핵심이었다. 실무적인 관점에서도 이는 복잡한 상태 전이를 반대 방향으로 바라보면 더 간단한 규칙으로 바뀔 수 있다는 점을 보여준다.

이미지 2 — 점프와 순간 이동: 목표 지점에서 역방향으로 계산해 배터리 사용량 최소값을 검증한 화면

이미지 2 — 점프와 순간 이동: 목표 지점에서 역방향으로 계산해 배터리 사용량 최소값을 검증한 화면

8. 문제 해결 경험 3 — 캐릭터의 좌표: 이동보다 먼저 경계 조건을 정의하기

캐릭터의 좌표 문제에서는 방향 입력에 따라 좌표를 이동시키는 것 자체는 어렵지 않았다. 하지만 실제로 헷갈린 부분은 보드 밖으로 나가지 않도록 하는 경계 조건이었다. 특히 up, down, left, right가 각각 x, y에 어떤 영향을 주는지, 그리고 이동 가능한 최대 좌표가 어떻게 결정되는지를 명확히 해야 했다.

원인은 좌표계를 먼저 고정하지 않은 상태에서 조건을 작성하려고 했기 때문이다. 보드 크기가 [11, 11]이면 이동 가능한 x좌표는 -5부터 5까지이고, y좌표도 -5부터 5까지다. 따라서 보드의 절반 크기를 기준으로 map_x = board[0] // 2, map_y = board[1] // 2를 구해야 한다. 이후 왼쪽 이동은 x > -map_x일 때만 가능하고, 오른쪽 이동은 x < map_x일 때만 가능하다. 위쪽 이동은 y < map_y, 아래쪽 이동은 y > -map_y일 때만 가능하다.

조치는 현재 좌표를 x = 0, y = 0으로 초기화한 뒤, 각 입력에 대해 이동 가능 여부를 먼저 검사하고 좌표를 갱신하는 방식으로 구현했다. 이 방식은 이동 후에 보정하는 방식보다 명확하다. 애초에 이동할 수 없는 명령은 무시되기 때문이다.

up = [0, 1]
down = [0, -1]
left = [-1, 0]
right = [1, 0]
def solution(keyinput, board):
    #1. board의 가로 크기와 세로 크기
    board_width = board[0]
    board_height = board[1]
    map_x = board[0] // 2
    map_y = board[1] // 2
    x = 0
    y = 0
    for i in keyinput:
        if i =="left" and x > -map_x:
            x -= 1
        if i =="right" and x < map_x:
            x += 1
        if i == "up" and y < map_y:
            y += 1
        if i == "down" and y > -map_y:
            y -= 1
    return [x, y]
print(solution(["left", "right", "up", "right", "right"], [11, 11]))
# [2, 1]
print(solution(["down", "down", "down", "down", "down"], [7, 9]))
# [0, -4]

검증 결과 첫 번째 테스트에서는 [2, 1], 두 번째 테스트에서는 [0, -4]가 반환되었다. 특히 두 번째 예시는 아래쪽으로 계속 이동하려고 해도 보드의 하단 경계를 넘지 않고 -4에서 멈춘다는 점을 확인할 수 있다. 이 문제를 통해 좌표 이동 문제에서는 이동 명령보다 경계 조건을 먼저 정의해야 한다는 점을 정리했다.

이미지 3 — 캐릭터의 좌표: 방향 입력과 보드 경계 조건을 함께 검증해 정답 제출을 완료한 화면

이미지 3 — 캐릭터의 좌표: 방향 입력과 보드 경계 조건을 함께 검증해 정답 제출을 완료한 화면

9. 문제 해결 경험 4 — 멀쩡한 사각형: 예시에서 맞는 코드와 일반 공식의 차이 구분하기

멀쩡한 사각형 문제는 전체 격자 수에서 대각선이 지나가며 사용할 수 없게 되는 칸을 빼는 문제였다. 처음에는 w = 8, h = 12 예시에서 못 쓰는 칸이 16개이므로 w * 2처럼 단순하게 생각할 수 있었다. 실제로 8 * 2 = 16이기 때문에 예시 하나만 보면 맞는 것처럼 보인다.

그러나 이 접근의 문제는 높이 h와 최대공약수 구조를 반영하지 못한다는 점이었다. 예를 들어 3 x 3에서는 대각선이 지나가는 칸이 3개인데, w * 2로 계산하면 6개가 되어 틀린다. 즉 예시에서는 우연히 맞았지만 일반적인 입력에서는 사용할 수 없는 규칙이었다.

조치는 이 문제를 공식형 문제로 정리하는 것이었다. 전체 칸 수는 w * h이고, 대각선이 지나가며 사용할 수 없게 되는 칸 수는 w + h - gcd(w, h)이다. 여기서 gcd(w, h)는 가로와 세로의 최대공약수이며, 대각선이 반복되는 격자 패턴을 결정하는 값으로 볼 수 있다. 이 공식 자체의 유도는 처음에는 직관적으로 어렵기 때문에, 풀이에서는 공식 적용을 명확히 하고 코드로 안정적으로 구현하는 것을 우선했다.

# 가로 W, 세로의 길이 H, 사용할 수 있는 정사각형의 개수
from math import gcd
def solution(w,h):
    total = w * h
    broken = w + h - gcd(w, h)
    return total - broken
print(solution(8, 12))
# 80

확인 결과 solution(8, 12)80을 반환했다. 이 문제에서 중요한 학습 지점은 공식 자체보다도 “예시에서 우연히 맞는 규칙을 일반 공식으로 착각하면 안 된다”는 점이었다. 따라서 최종 코드는 전체 칸 수와 못 쓰는 칸 수를 분리하여 작성했고, broken이라는 변수로 대각선이 망가뜨리는 칸의 의미를 명확히 했다.

이미지 4 — 멀쩡한 사각형: 대각선이 지나가는 칸 수를 공식으로 정리하고 예시 결과 80을 검증한 화면

이미지 4 — 멀쩡한 사각형: 대각선이 지나가는 칸 수를 공식으로 정리하고 예시 결과 80을 검증한 화면

10. 문제 해결 경험 5 — 비밀 코드 해독: 후보 생성과 조건 검사를 분리하기

비밀 코드 해독 문제에서는 qans의 의미를 정확히 잡는 것이 핵심이었다. q는 사용자가 입력한 5개 숫자 조합의 목록이고, ans는 각 질문에 대해 비밀 코드와 몇 개가 일치했는지를 알려주는 응답이다. 처음에는 ans.append(match_count)처럼 내가 계산한 값을 ans에 추가해야 하는 것처럼 생각할 수 있었다. 하지만 실제로 ans는 문제에서 이미 주어진 정답 조건이며, 내가 해야 할 일은 후보와 질문의 일치 개수를 계산해 ans[i]와 비교하는 것이었다.

문제의 원인은 “정답 배열을 만드는 문제”와 “주어진 응답 조건을 검증하는 문제”를 혼동한 데 있었다. 이 문제는 진짜 비밀 코드를 직접 찾는 문제가 아니라, 비밀 코드가 될 수 있는 가능한 조합의 개수를 세는 문제다. 따라서 먼저 1부터 n까지의 숫자 중 5개를 고른 모든 후보를 만들고, 각 후보가 모든 질문과 응답 조건을 만족하는지 확인해야 한다.

조치는 combinations(range(1, n + 1), 5)로 후보를 만들고, 각 후보에 대해 q의 모든 질문을 검사하는 방식으로 구현했다. 후보와 질문의 일치 개수는 match_count로 세고, 이 값이 expected_count = ans[i]와 다르면 해당 후보는 탈락한다. 이를 표시하기 위해 passed 변수를 사용했다. passed는 후보가 모든 조건을 통과했는지 기록하는 플래그다.

# 1부터 n까지 서로 다른 정수 5개 오름차순 = 비밀코드. 맞춰야.
# m번 시도 가능.
# 입력한 정수 배열 q.
# 시스템응답(일치하는 개수)=비밀코드에 포함된 정수 개수 알려줌 = ans
# m번 시도 후, 비밀코드로 가능한, 정수 조합의 개수를 알고 싶음.
# 비밀코드로 가능한 정수 조합 개수 return
from itertools import combinations
def solution(n, q, ans):
    answer = 0
    for candidate in combinations(range(1, n + 1), 5):
        passed = True
        for i in range(len(q)):
            question = q[i]
            expected_count = ans[i]
            match_count = 0
            for number in question:
                if number in candidate:
                    match_count += 1
            if match_count != expected_count:
                passed = False
                break
        if passed:
            answer += 1
    return answer
print(solution(
    10,
    [
        [1, 2, 3, 4, 5],
        [6, 7, 8, 9, 10],
        [3, 7, 8, 9, 10],
        [2, 5, 7, 9, 10],
        [3, 4, 5, 6, 7]
    ],
    [2, 3, 4, 3, 3]
))
# 예상 결과: 3

확인 결과 예시 입력에서 가능한 비밀 코드 조합 개수는 3으로 계산되었다. 이 문제를 통해 완전탐색 문제에서는 “후보 생성”과 “조건 검사”를 섞지 않는 것이 중요하다는 점을 확인했다. 또한 ans처럼 문제에서 이미 주어진 값은 새로 만드는 대상이 아니라 비교 기준이라는 점을 코드 구조에 반영했다.

이미지 5 — 비밀 코드 해독: 모든 5개 후보 조합을 생성하고 q와 ans 조건을 검증해 정답을 도출한 화면

이미지 5 — 비밀 코드 해독: 모든 5개 후보 조합을 생성하고 q와 ans 조건을 검증해 정답을 도출한 화면

11. 문제 해결 경험 6 — 요격 시스템: 개구간 조건과 끝점 기준 정렬로 최소 발사 수 구하기

요격 시스템 문제는 여러 폭격 미사일 구간을 최소한의 요격 미사일로 모두 맞추는 문제다. 처음에는 구간이 겹치는지 하나씩 비교해야 할 것처럼 보였지만, 실제 핵심은 “어디에 쏘면 가장 안정적으로 많은 미사일을 처리할 수 있는가”였다. 이 문제에서는 가장 빨리 끝나는 미사일부터 처리하고, 그 끝점 바로 왼쪽에 쐈다고 생각하는 전략이 필요했다.

가장 헷갈린 부분은 구간이 (s, e)라는 점이었다. 일반적으로 [s, e]처럼 닫힌 구간이면 시작점이나 끝점에서도 포함된다고 생각할 수 있다. 하지만 이 문제는 개구간이므로 se에서는 요격할 수 없다. 따라서 이전 발사 기준점과 현재 구간의 시작점이 같아도 맞춘 것으로 볼 수 없다. 이 때문에 조건은 start > shoot이 아니라 start >= shoot이 되어야 한다.

또한 shoot = end도 처음에는 헷갈릴 수 있다. 이것은 실제로 end에 쐈다는 뜻이 아니다. 문제의 구간이 개구간이므로 end에서는 맞출 수 없다. 따라서 의미상으로는 end 바로 왼쪽에 쐈다고 생각하고, 비교를 쉽게 하기 위해 shootend 값을 저장하는 것이다. 처음에는 shoot = -1 같은 임시값도 사용할 수 있지만, 아직 쏜 적이 없다는 의미를 명확히 하기 위해 None을 사용했다.

# 정수 쌍(s, e)형태
# targets: 각 폭격 미사일의 x좌표 범위 목록
# 특정 x좌표에 걸쳐있는 모든 폭격 미사일을 관통하여 한번에 요격할 수 있다.
# (s, e)로 표현되는 폭격 미사일은 s와 e에서 발사하는 요격 미사일로는 요격할 수 없다.
# 모든 폭격 미사일을 요격하기 위해 필요한 요격 미사일 수의 최솟값
# 현재 미사일의 시작점이 이전 발사 위치보다 오른쪽이거나 같으면 이전 발사로는 못 맞춘다.
def solution(targets):
    # 끝점 기준으로 target을 정렬하기.
    targets.sort(key=lambda target: target[1])
    # 답은 요격 미사일 수
    answer = 0
    # 이전에 요격 미사일을 쏜 위치
    shoot = None
    for start, end in targets:
        # 아직 쏜적이 없거나,
        # 이전 요격 위치로 현재 미사일을 맞출 수 없으면 새로 쏜다.
        if shoot is None or start >= shoot:
            # 요격 미사일을 새로 쏜다.
            answer += 1
            # 이번 요격 위치의 기준을 현재 구간의 end로 갱신한다.
            shoot = end
    return answer
print(solution([[4,5],[4,8],[10,14],[11,13],[5,12],[3,7],[1,4]]))
# 3
print(solution([[1, 4], [2, 5], [3, 6]]))
# 1
print(solution([[1, 4], [4, 5]]))
# 2
print(solution([[1, 3], [3, 5], [5, 7]]))
# 3
print(solution([[1, 10], [2, 9], [3, 8], [4, 7]]))
# 1

검증 결과 기본 예시에서는 3이 반환되었고, 추가 테스트를 통해 겹치는 구간은 한 번에 처리되며, 끝점과 시작점이 같은 개구간은 따로 처리되어야 한다는 점도 확인했다. 이 문제는 정렬 기준과 경계 조건이 모두 중요한 그리디 문제였고, 특히 start >= shoot 조건은 문제의 개구간 제약을 코드로 반영한 핵심이었다.

이미지 6 — 요격 시스템: 개구간 조건과 끝점 기준 정렬을 반영해 최소 요격 수를 검증한 화면

이미지 6 — 요격 시스템: 개구간 조건과 끝점 기준 정렬을 반영해 최소 요격 수를 검증한 화면

12. 복잡한 문제 해결 경험

이번 실습에서 가장 복잡하게 느껴진 문제는 크게 세 가지였다. 첫 번째는 멀쩡한 사각형, 두 번째는 비밀 코드 해독, 세 번째는 요격 시스템이었다. 세 문제 모두 단순 반복문만으로는 풀이의 이유가 바로 보이지 않는다는 공통점이 있었다.

멀쩡한 사각형은 공식 자체가 납득하기 어려웠다. w + h - gcd(w, h)라는 식이 갑자기 등장하는 것처럼 보였고, gcd가 왜 필요한지도 직관적으로 받아들이기 어려웠다. 이 문제에서 실제로 중요한 전환은 “모든 공식을 완전히 유도해야만 풀 수 있다”는 생각을 내려놓고, 코딩테스트에서는 공식형 문제를 공식 적용 문제로 정리할 수도 있다는 점이었다. 즉 전체 칸 수는 w * h, 못 쓰는 칸 수는 w + h - gcd(w, h)로 분리해 코드에 반영했다.

비밀 코드 해독은 변수의 역할이 복잡했다. q, ans, candidate, question, expected_count, match_count, passed가 각각 다른 의미를 가진다. 처음에는 ans를 새로 만드는 값처럼 생각할 수 있었지만, 실제로는 이미 주어진 응답 조건이다. 따라서 내가 계산해야 하는 값은 match_count이고, 이 값이 ans[i]와 같은지 확인해야 한다. 이 문제는 후보를 만들고, 조건을 검사하고, 통과 여부를 기록하는 구조로 분리하면서 해결할 수 있었다.

요격 시스템은 경계 조건이 복잡했다. start >= shoot에서 왜 같은 경우도 새로 쏴야 하는지가 핵심이었다. 이 조건은 일반적인 구간 문제가 아니라, 문제에서 (s, e)라는 개구간을 사용했기 때문에 필요했다. 또한 shoot = end는 실제 발사 위치가 아니라 end 바로 왼쪽에 쐈다고 생각하기 위한 기준값이다. 이 차이를 이해하면서 코드가 단순한 비교식이 아니라 문제 조건을 압축한 표현이라는 점을 확인했다.

13. 성과

이번 실습을 통해 6개의 문제를 모두 정답 처리했다. 각 문제는 제출 화면에서 정답으로 확인되었고, 점수도 누적되었다. 카펫, 점프와 순간 이동, 캐릭터의 좌표, 멀쩡한 사각형, 비밀 코드 해독, 요격 시스템까지 서로 다른 유형의 문제를 풀며 총점이 상승하는 결과를 확인했다.

성과는 단순히 정답 개수가 늘어난 것에만 있지 않다. 문제마다 막혔던 지점을 코드 구조로 정리하면서, 다음에 유사한 문제가 나왔을 때 어떤 기준을 먼저 잡아야 하는지 정리할 수 있었다. 격자 문제에서는 내부 구조와 경계 조건을 먼저 보고, 조합 문제에서는 후보 생성과 조건 검사를 분리하며, 구간 문제에서는 정렬 기준과 포함 여부를 먼저 확인해야 한다는 기준이 생겼다.

특히 이번 실습에서는 “왜 이 코드가 필요한가”를 계속 질문하며 풀이를 구성했다. 그 결과 단순히 정답 코드를 외우는 방식이 아니라, 문제 문장을 변수와 조건으로 바꾸는 훈련이 되었다. 이는 코딩테스트뿐만 아니라 실제 개발에서도 요구사항을 정확히 해석하고, 예외 조건을 코드에 반영하는 능력과 연결된다.

16. 사용한 주요 수식/코드 정리

16–1. 카펫 — 노란색 내부 영역에서 전체 카펫 크기 복원

이 코드는 노란색 영역을 기준으로 전체 카펫의 크기를 복원하기 위해 사용했다. 문제는 brownyellow의 개수만 주어졌을 때 전체 가로와 세로를 찾아야 한다는 점이었다. 노란색 영역의 세로 길이를 하나씩 가정하고, 나누어떨어지는 경우에만 가로 길이를 계산했다.

for yellow_height in range(1, yellow +1):
    if yellow % yellow_height == 0:
        yellow_width = yellow // yellow_height
        brown_width = yellow_width + 2
        brown_height = yellow_height +2

이 식은 노란색 영역 바깥에 갈색 테두리가 한 칸씩 생긴다는 구조를 반영한다. 따라서 전체 가로와 세로는 노란색 가로·세로보다 각각 2칸씩 커진다.

13–2. 점프와 순간 이동 — 짝수와 홀수로 역방향 처리

이 코드는 목표 지점에서 거꾸로 출발해 배터리 사용량을 계산하기 위해 사용했다.

while n>0:
    if n % 2 == 0:
        n = n//2
    else:
        battery += 1
        n -= 1

짝수는 순간이동으로 도달한 위치로 볼 수 있으므로 배터리 사용 없이 절반으로 줄인다. 홀수는 순간이동만으로 만들 수 없으므로 1칸 점프가 필요했다고 보고 배터리를 1 증가시킨다.

13–3. 캐릭터의 좌표 — 경계 조건 검사 후 이동

이 코드는 보드 밖으로 나가지 않도록 이동 전에 조건을 검사한다.

if i =="left" and x > -map_x:
    x -= 1
if i =="right" and x < map_x:
    x += 1
if i == "up" and y < map_y:
    y += 1
if i == "down" and y > -map_y:
    y -= 1

각 조건은 이동 가능 여부를 먼저 판단한다. 이 방식은 이동 후 좌표를 되돌리는 것보다 문제의 제한 조건을 명확히 표현한다.

13–4. 멀쩡한 사각형 — 대각선이 망가뜨리는 칸 수 계산

이 문제의 핵심 공식은 다음과 같다.

broken = w + h - gcd(w, h)

전체 칸 수는 w * h이고, 대각선이 지나가며 사용할 수 없게 되는 칸 수는 w + h - gcd(w, h)이다. 따라서 최종 답은 다음과 같다.

return total - broken

이 코드는 예시에서 우연히 맞는 계산이 아니라, 모든 입력에 대해 적용 가능한 공식으로 문제를 해결한다.

13–5. 비밀 코드 해독 — 후보와 질문의 일치 개수 검사

이 문제의 핵심 코드는 후보와 질문이 몇 개 겹치는지 세는 부분이다.

match_count = 0
for number in question:
    if number in candidate:
        match_count += 1

이렇게 계산한 match_count는 문제에서 주어진 ans[i]와 비교된다.

if match_count != expected_count:
    passed = False
    break

하나라도 조건이 맞지 않으면 해당 후보는 비밀 코드가 될 수 없으므로 즉시 탈락시킨다.

13–6. 요격 시스템 — 끝점 기준 정렬과 개구간 조건

요격 시스템의 핵심 코드는 다음과 같다.

targets.sort(key=lambda target: target[1])

이 코드는 미사일을 끝나는 지점 기준으로 정렬한다. 가장 빨리 끝나는 미사일부터 처리해야, 그 끝점 바로 왼쪽에 쏘는 전략으로 최대한 많은 구간을 함께 처리할 수 있다.

if shoot is None or start >= shoot:
    answer += 1
    shoot = end

이 조건은 아직 쏜 적이 없거나, 이전 발사 기준으로 현재 미사일을 맞출 수 없을 때 새로 쏜다는 뜻이다. 특히 start == shoot인 경우도 새로 쏴야 하는데, 문제에서 구간이 (s, e)인 개구간으로 주어져 시작점에서는 맞출 수 없기 때문이다.

14. 최종 정리

이번 6개 문제 실습은 문제 유형별로 다른 풀이 기법을 사용했지만, 공통적으로 “조건 해석”이 가장 중요했다. 카펫과 캐릭터의 좌표는 격자와 경계 조건을 정확히 잡아야 했고, 멀쩡한 사각형은 예시에서 우연히 맞는 식과 일반 공식을 구분해야 했다. 비밀 코드 해독은 후보 생성과 조건 검사를 분리해야 했으며, 요격 시스템은 개구간 조건과 정렬 기준을 함께 고려해야 했다.

이번 기록에서 가장 의미 있는 점은 내가 어려워한 부분을 그대로 문제 해결 과정으로 전환했다는 것이다. gcd가 왜 필요한지 이해하기 어려웠던 경험, qans의 역할이 헷갈렸던 경험, start >= shoot에서 왜 같은 경우도 새로 쏴야 하는지 의문을 가진 경험은 모두 실패가 아니라 문제 조건을 더 정확히 해석하게 만든 계기가 되었다.

앞으로 유사한 문제를 풀 때는 먼저 문제의 핵심 문장을 변수로 바꾸고, 그다음 조건문을 설계하는 방식으로 접근할 수 있다. 특히 코딩테스트에서는 정답 코드를 빠르게 작성하는 것도 중요하지만, 그 코드가 어떤 제약을 반영하고 있는지 설명할 수 있어야 한다. 이번 실습은 그 설명 가능성을 높이는 과정이었다.

15. Portfolio Summary

This practice session focused on transforming problem statements into executable Python logic across six coding test problems. The work covered grid reconstruction, reverse calculation, coordinate boundary handling, formula-based geometry, combinational candidate filtering, and greedy interval scheduling. Rather than treating each problem as an isolated exercise, the practice emphasized how to identify the core condition, convert it into variables, and validate the result with test cases.

A key outcome of this session was the ability to handle confusion as part of the problem-solving process. Difficult points such as interpreting open intervals, understanding the role of gcd, separating generated candidates from given constraints, and defining boundary conditions were not ignored. They were analyzed, restructured, and converted into reliable code. This makes the practice valuable as a portfolio artifact because it shows not only successful submissions, but also the reasoning process behind the solutions.

The session also demonstrates practical engineering habits: checking assumptions against edge cases, avoiding example-specific shortcuts, using clear variable names, and writing verification tests after implementation. These habits are directly relevant to software development because real-world implementation often requires the same process: read the requirement, identify hidden constraints, model the logic, and verify behavior through representative cases.

16. Key skills practiced

  1. Python function design for coding test solutions
  2. Grid-based problem interpretation
  3. Boundary condition handling in coordinate systems
  4. Reverse reasoning for optimization problems
  5. Mathematical formula application with gcd
  6. Candidate generation using itertools.combinations
  7. Full-search validation with condition filtering
  8. Greedy algorithm design using interval end-time sorting
  9. Open interval interpretation and edge-case handling
  10. Debugging by comparing expected and actual logic
  11. Writing readable variable names for problem constraints
  12. Test case construction for correctness verification
  13. Separating candidate generation from condition checking
  14. Translating Korean problem statements into code conditions
  15. Recognizing when an example-specific rule is not a general solution

메타데이터
post_id
536598dabebf
slug
ct스터디-코딩테스트-입문-실전-문제를-풀며-조건-해석과-탐색-기준을-재정의한-문제-해결-기록-536598dabebf
url
https://medium.com/@jmin54492/ct%EC%8A%A4%ED%84%B0%EB%94%94-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%85%EB%AC%B8-%EC%8B%A4%EC%A0%84-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%ED%92%80%EB%A9%B0-%EC%A1%B0%EA%B1%B4-%ED%95%B4%EC%84%9D%EA%B3%BC-%ED%83%90%EC%83%89-%EA%B8%B0%EC%A4%80%EC%9D%84-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EA%B8%B0%EB%A1%9D-536598dabebf
canonical_url
https://medium.com/@jmin54492/ct%EC%8A%A4%ED%84%B0%EB%94%94-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%85%EB%AC%B8-%EC%8B%A4%EC%A0%84-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%ED%92%80%EB%A9%B0-%EC%A1%B0%EA%B1%B4-%ED%95%B4%EC%84%9D%EA%B3%BC-%ED%83%90%EC%83%89-%EA%B8%B0%EC%A4%80%EC%9D%84-%EC%9E%AC%EC%A0%95%EC%9D%98%ED%95%9C-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EA%B8%B0%EB%A1%9D-536598dabebf
author_url
https://medium.com/@jmin54492
status
ok
fetched_at
2026-07-13 06:23:13