Skip to content

Conversation

@Mingguriguri
Copy link
Collaborator

🌱WIL

이번 한 주의 소감을 작성해주세요!

  • 이번 주에는 주로 DFS, 백트래킹, BFS 유형 위주로 풀이했다. 솔직히 너무 풀기 싫었지만,, 풀다 보면 정말 많이 배울 수 있는 유형인 것 같다.
  • 몇몇 문제는 Java로도 풀이했다. 파이썬이 훨씬 편했다는 걸 더 느끼게 되었다. 그래도 이런 식으로 몇몇 문제는 자바로 푸는 연습을 꾸준히 하는 게 좋을 것 같다.

🚀주간 목표 문제 수: 3개

푼 문제


프로그래머스 #43165. 타겟 넘버(java): 그래프 / Level 2

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  1. 정답의 개수를 저장할 전역 변수 count를 0으로 초기화한다.
  2. backtracking() 함수에서는
    1. 매 단계마다 두 가지 (+ 더하기, - 빼기) 선택을 재귀적으로 수행한다.
    2. 만약 모든 숫자를 순회했을 경우 종료한다.
      • 이때 현재 합계와 target값이 같을 경우 count를 증가시킨다.
  3. solution() 함수에서는
    • backtracking(0, 0)을 호출하여 탐색을 시작한다.
    • 탐색 종료 후 count를 반환한다.

🚩제출한 코드

class Solution {
    int count = 0;
    
    public int solution(int[] numbers, int target) {
        backtracking(0, 0, numbers, target);
    
        return count;
    }
    
    public void backtracking(int idx, int currSum, int[] numbers, int target) {
        if (idx == numbers.length) {
            if (currSum == target) count++;
            return;
        }
        
        backtracking(idx + 1, currSum + numbers[idx], numbers, target);
        backtracking(idx + 1, currSum - numbers[idx], numbers, target);
    }
}

💡TIL

배운 점이 있다면 입력해주세요
백트래킹 문제라고 생각하고 아래와 같은 조건을 넣었지만, 실제로는 불필요했다.

# 종료조건 2: 현재 합이 target보다 클 경우, 더 탐색할 필요가 없음
if curr_sum > target:
    return

왜냐하면 curr_sum은 음수도 될 수 있고, target도 0보다 클 수도 작을 수도 있기 때문

이다. 즉, curr_sum > target 이라는 조건은 유효하지 않았기 때문이다.

경우에 따라 뒤에서 -가 계속 붙으면 결국 target을 맞출 수도 있기 때문이다.


백준 #15683. 감시 - 백트래킹: 구현 / 골드3

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  • office: 사무실 상태 입력받기
  • cctvs: CCTV 위치와 번호 저장
  • directions: 상, 우, 하, 좌 방향 정의
  • cctv_dirs: 각 CCTV 번호별 가능한 방향 세트 정의
  • count_zero(): 감시받지 못한 칸(0) 개수 세기
  • watch(): 특정 방향으로 감시 수행
  • dfs():
    • 모든 CCTV를 순서대로 하나씩 방향 조합을 적용
    • 적용한 보드를 깊은 복사 후 다음 CCTV 처리
    • 모든 CCTV의 방향이 결정되면 사각지대 개수 계산 및 최소값 갱신

🚩제출한 코드

import sys
import copy
input = sys.stdin.readline

N, M = map(int, input().split())
office = [list(map(int, input().split())) for _ in range(N)]

# CCTV 위치 저장
cctvs = []
for i in range(N):
    for j in range(M):
        if 1 <= office[i][j] <= 5:
            cctvs.append((i, j, office[i][j]))

total_cctv = len(cctvs) # 전체 CCTV 개수
min_blind_spot = 1e9    # 정답으로 반환할 사각지대 개수

# CCTV별 방향 설정
directions = [(-1, 0), (0, 1), (1, 0), (0, -1)] # 상 -> 우 -> 하 -> 좌
cctv_dirs = {
    1: [[0], [1], [2], [3]],
    2: [[0, 2], [1, 3]],
    3: [[0, 1], [1, 2], [2, 3], [3, 0]],
    4: [[0, 1, 2], [1, 2, 3], [2, 3, 0], [3, 0, 1]],
    5: [[0, 1, 2, 3]]
}

# 사각지대 개수 구하는 함수
def count_zero(temp):
    cnt = 0
    for i in range(N):
        for j in range(M):
            if temp[i][j] == 0:
                cnt += 1
    return cnt

# DFS 함수
def dfs(idx, office):
    global min_blind_spot
    # 종료 조건
    if idx == total_cctv:
        # 종료할 때는 사각지대(= 0) 개수 세고, 최소값 갱신해야 한다.
        blind_spot = count_zero(office)
        min_blind_spot = min(min_blind_spot, blind_spot)
        return

    x, y, number = cctvs[idx]
    for cctv_dir in cctv_dirs[number]:
        # temp에 현재 office 리스트 깊은 복사
        temp = copy.deepcopy(office)
        
        for dir in cctv_dir:
            dx, dy = directions[dir]
            nx, ny = x + dx, y + dy
            
            while 0 <= nx < N and 0 <= ny < M and temp[nx][ny] != 6:
                if temp[nx][ny] == 0:
                    temp[nx][ny] = '#'  # CCTV 감시OK
                nx += dx
                ny += dy

        dfs(idx + 1, temp)


dfs(0, office)
print(min_blind_spot)

💡TIL

배운 점이 있다면 입력해주세요

  • CCTV 번호별로 방향 탐색 조건을 잘 설정하고 탐색한 후 다시 원래상태로 되돌리기만 잘 한다면 전체적으로 전형적인 백트래킹 탐색 구조를 따르기 때문에 큰 어려움 없이 풀 수 있었다. 다만 CCTV를 방향별로 탐색하는 아이디어를 떠올리는 것이 어려웠다.
  • 원래 상태로 되돌리기 위해서 copy()를 썼다. 하지만 copy()는 얕은 복사 함수이기 때문에 깊은 복사가 되지 않고, 동일한 주소값을 가진다. 따라서 꼭 깊은 복사할 때에는 copy.deepcopy() 를 사용하도록 하자.
  • 튜플과 리스트의 차이점은 ‘수정’이 안 되는 것인 줄 알았는데 하나 더 발견했다. 인자가 1개인 경우, 튜플은 iterable 연산이 되지 않는다. 리스트는 for loop를 돌 때 인자가 하나만 있어도 돌아가지만, 튜플은 오류가 발생한다. 이러한 이유 때문에 cctv_dirs에서도 튜플이 아닌 리스트로 변경했다.

프로그래머스 #1844. 게임 맵 최단거리(java): 그래프 / Level 2

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  • visited[n][m]: 방문 여부 체크 배열
  • queue: BFS 탐색용 큐
  • dirs: 상하좌우 이동을 위한 방향 벡터
  • 큐에서 좌표를 꺼내 다음 좌표(nx, ny)를 계산
    • 조건: 맵 범위 내 & 벽이 아니며 & 아직 방문하지 않은 곳
    • 이동 가능하다면 현재 칸 수 + 1로 업데이트 후 큐에 삽입
  • 목표 지점에 도달하면 maps[n-1][m-1] 반환
  • 도달하지 못했으면 1 반환

🚩제출한 코드

import java.util.*;

class Solution {
    public int solution(int[][] maps) {
        int n = maps.length;
        int m = maps[0].length;

        // 방문여부 리스트
        boolean[][] visited = new boolean[n][m];
        visited[0][0] = true;
        
        // 방향 벡터 만들기
        int[][] dirs ={{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

        // 큐 선언
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0});
        
        // BFS 탐색
        while (!queue.isEmpty()) {
            int[] current = queue.poll();
            int x = current[0];
            int y = current[1];
            
            for (int[] dir : dirs) {
                int nx = x + dir[0];
                int ny = y + dir[1];
                
                if ((0 <= nx && nx < n && 0 <= ny && ny < m) 
                    && !visited[nx][ny] 
                    && maps[nx][ny] == 1) {
                    maps[nx][ny] = maps[x][y] + 1;
                    visited[nx][ny] = true;
                    queue.add(new int[]{nx, ny});
                }                
            }
        }
        
        if (maps[n-1][m-1] != 1) {
            return maps[n-1][m-1];
        }
        return -1;
    }
}

💡TIL

배운 점이 있다면 입력해주세요
무난한 문제였다.

자바로 풀이하는 과정이 파이썬에 비해 까다로웠다. 배열 선언이나 출력 방식에 있어서 익숙하지 않아서 시행착오가 있었다.

자바에서 2차원 배열을 출력하려고 System.out.println(dirs.toString());를 쓰면, 배열의 메모리 주소만 출력된다. 배열 내용을 보기 위해서는 Arrays.deepToString()을 사용해야 한다.

또한 자바에서 좌표를 관리할 때 Point 클래스를 활용할 수 있다는 점도 배우게 되었다.

다만 문제 풀이에서는 별도의 클래스를 만들지 않고 int[] 배열로 처리하였다.


백준 #16987. 란으로 계란치기 - 백트래킹: 구현 / 골드5

정리한 링크: (바로가기)

🚩플로우 (선택)

코드를 풀이할 때 적었던 플로우가 있나요?

  1. DFS 탐색을 통해 모든 가능한 계란 조합을 시도한다.
  2. 종료 조건:
    • 현재 인덱스가 계란 개수 N과 같다면 ⇒ 모든 계란을 한 번씩 다 든 상태이므로 종료.
      • 이때 최대로 깨진 값(max_broken)값을 갱신한다.
    • 현재 계란이 깨졌다면 ⇒ 다음 계란으로 넘어간다.
  3. 반복문을 통해 현재 계란으로 칠 수 있는 계란을 모두 시도한다:
    • 자기 자신이거나, 이미 깨진 계란은 건너뛴다.(if i == cur_idx or eggs[i][0] <= 0)
    • 현재 탐색 중인 계란(eggs[cur_idx])과 순회 중인 계란(eggs[i])을 서로 타격시킨다.
    • DFS 함수를 재귀 호출한다.
    • 재귀 이후에는 현재 탐색 중인 계란(eggs[cur_idx])과 순회 중인 계란(eggs[i])의 상태를 원래대로 되돌린다.
  4. 반복문 내에서 칠 수 있는 계란이 한 번도 없었다면 다음 계란으로 넘어가야하므로 DFS 함수를 호출해야 한다.
  5. 탐색이 모두 종료되면 max_broken 값을 출력한다.

🚩제출한 코드

import sys
input = sys.stdin.readline

N = int(input()) # 계란의 수
eggs = [list(map(int, input().split())) for _ in range(N)] # 계란의 내구도와 무게를 담은 리스트

max_broken = 0 # 최대 깨진 계란 수를 저장하는 변수

def count_broken_eggs(eggs):
    # 깨진 계란의 수 구하기
    count = 0
    for S, W in eggs:
        if S <= 0:
            count += 1
    return count

def dfs(cur_idx):
    global max_broken
    # 모든 계란을 한 번씩 든 경우
    if cur_idx == N:
        # 깨진 계란의 수 세고 max_broken 갱신
        max_broken = max(max_broken, count_broken_eggs(eggs))
        return

    # 현재 계란이 깨졌다면 다음 계란으로 넘어간다.
    if eggs[cur_idx][0] <= 0:
        dfs(cur_idx + 1)
        return

    broken = False # 현재 계란으로 다른 계란을 깼는지 여부
    for i in range(N):
        if i == cur_idx or eggs[i][0] <= 0:
            continue

        # 계란끼리 치기
        eggs[cur_idx][0] -= eggs[i][1]
        eggs[i][0] -= eggs[cur_idx][1]
        broken = True

        # 탐색
        dfs(cur_idx + 1) # 다음 계란으로 넘어간다.

        # 상태 복구
        eggs[cur_idx][0] += eggs[i][1]
        eggs[i][0] += eggs[cur_idx][1]
        
    # 칠 수 있는 계란이 없었던 경우, 다음으로 넘어가야 한다.
    if not broken:
        dfs(cur_idx + 1)

dfs(0)
print(max_broken)

💡TIL

배운 점이 있다면 입력해주세요
이 문제를 풀면서 가장 어려웠던 부분은, 현재 계란이 깨졌거나 칠 수 있는 계란이 없는 조건을 정확하게 처리하지 못한 것이었다. 초반에는 단순히 “모든 계란이 깨졌는가?”만 판단했는데, 실제로는 현재 계란이 칠 수 있는 대상이 있는지 여부를 잘 따져야 했다.

이를 해결하기 위해 broken이라는 불리언 변수를 두고, 실제로 한 번이라도 계란을 쳤는지를 체크했다. 만약 한 번도 치지 못했다면 그 경우에도 다음 계란으로 넘어가야 한다는 점이 중요하다.
처음에는 조건 설정을 잘못해서 예제 테스트 케이스를 통과하지 못했지만, 다른 사람의 풀이를 참고하면서 많이 배웠다.

@Mingguriguri Mingguriguri self-assigned this Jul 26, 2025
@Mingguriguri Mingguriguri merged commit 5a1decc into main Jul 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants