Post

[코딩테스트 합격자 되기 스터디] 7주차 - 백트래킹 (8.18 ~ 8.24)


코딩테스트 합격자 되기 스터디 7주차

강의 보기 -> 해당되는 부분 책 다시 읽기 -> 블로그 정리
강의는 C++로 되어 있지만 개념적인 내용이라 언어 상관 없이 보고 코드는 각 언어에 맞게 바꾸면 된다.
이번 주차가 일정의 마지막 주차인데 스터디 전체 참여율이 50%가 넘을 경우,
다음 주차도 계속 진행한다고 한다! 다음 주차도 하면 좋겠다ㅎㅎ

참고 강의 및 책

인프런 : 코딩테스트 합격자 되기 파이썬, C+_+ 저자님 인프런 강의 링크
Youtube : 코딩테스트 합격자 되기 파이썬, C+_+ 저자님 Youtube 링크
종이책 : 코딩테스트 합격자 되기 파이썬 편
종이책 : 코딩테스트 합격자 되기 C++ 편
종이책 : 코딩테스트 합격자 되기 자바스크립트 편
종이책 : 코딩테스트 합격자 되기 자바 편

백트래킹

  • 가장 최근에 방문했던 노드로 다시 돌아간다. (예시 : DFS)
  • 완전 탐색하지 말고, 내가 찾는 답일 가능성이 있는 경우에만 탐색한다.
    • 예를 들면, 노드의 값이 숫자의 합이고 찾는 숫자가 5일 경우
    • 탐색한 노드 값이 5를 초과했다면 다음 노드를 탐색해도 5를 초과한다.
    • 그래서 탐색을 진행해도 5를 찾을 수 없으므로 다시 돌아가 다른 곳을 탐색한다.
  • 탐색을 진행할 때 유효한 판단을 하는 부분을 구현해야 한다.
    • 이를 유망함수를 구현한다고 한다.
    • 예를 들어, 외출할 때 집에 물건을 놓고 왔을 경우
    • 다른 집에는 내 물건이 있을 리가 없으니 이 부분을 제외한다.
    • 나의 집으로 돌아가 찾아야 한다.
    • 나의 집이 유망함수다.
  • 백트래킹의 과정
    • 첫 번째, 상태 정의 : 문제의 각 단계에서 가능한 상태를 정의한다.
    • 두 번째, 유망 함수 : 현재 상태가 유망한 지 판단, 유망하지 않으면 탐색 정지
    • 세 번째, 해결책 확인 : 현재 상태가 문제의 해결책인지 판단한다.
    • 네 번째, 재귀 호출 : 유망한 상태로 이동하면서 문제를 해결한다.
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
      // 유망성 판단 함수
      boolean isPromising(int level, State state) {
          // 현재 상태가 유망한지 판단하는 로직을 구현합니다.
          return true; // 예시로 true 반환, 실제로는 로직을 구현해야 합니다.
      }
                                                                          //
      // 해결책 확인 함수
      boolean isSolution(State state) {
          // 현재 상태가 문제의 해결책인지 판단하는 로직을 구현합니다.
          return true; // 예시로 true 반환, 실제로는 로직을 구현해야 합니다.
      }
                                                                          //
      // 해결책 처리 함수
      void processSolution(State state) {
          // 해결책을 처리하는 로직을 구현합니다.
      }
                                                                          //
      // 백트래킹 함수
      void backtrack(int level, State state) {
          // 현재 상태가 해결책이면 처리
          if (isSolution(state)) {
              processSolution(state);
              return;
          }
          // 다음 가능한 상태를 탐색
          for (State nextState : possibleStates(state)) {
              if (isPromising(level, nextState)) {
                  backtrack(level + 1, nextState);
              }
          }
      }
    

백트래킹 예시 - 부분 합

  • {1, 2, 3, 4}로 이루어진 집합에서 숫자를 뽑아 합이 5인 조합 구하기
  • 완전 탐색으로 풀 경우 O(2^N)를 가진다.
    • 2 * 2 * 2 * … 2
  • 백트래킹으로 접근하기
    • 상태 정의 : 현재까지 선택한 숫자들의 합
    • 유망 함수 : 현재 부분 합이 목표 값을 초과하면 유망하지 않다고 판단
    • 해결책 확인 : 현재 부분 합이 목표 값과 일치하는지 확인
    • 재귀 호출 : 다음 숫자를 선택하여 부분합 갱신
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
      impor java.util.*;
                                                                          //
      public class CombinationFinder {
          // 사용할 숫자들
          private static List<Integer> nums = List.of(1, 2, 3, 4);
          // 목표 합
          private static int target = 5;
          // 현재 조합
          private static List<Integer> current = new ArrayList<>();
                                                                          //
          public static void main(String[] args) {
              findCombinations(0);
          }
                                                                          //
          private static void findCombinations(int index) {
              int sum = 0;
              for (int num : current) {
                  sum += num;
              }
                                                                                          //
              // 조건 1: 현재 조합으로 합이 target이 되면 결과를 출력하고 더 탐색하지 않음
              if (sum == target) {
                  for (int num : current) {
                      System.out.print(num + " ");
                  }
                  System.out.println();
                  return;
              }
                                                                                          //
              // 조건 2: 합이 target을 초과하면 더 탐색하지 않음
              if (sum > target) {
                  return;
              }
                                                                                          //
              // 유망한 경우에만 다음 숫자를 추가하여 탐색을 계속함
              for (int i = index; i < nums.size(); i++) {
                  current.add(nums.get(i));
                  findCombinations(i + 1);
                  current.remove(current.size() - 1); // 현재 숫자를 조합에서 제외하여 다음 경우를 탐색
              }
          }
      }
    

백트래킹 예시 - N 퀸

  • 체스의 퀸을 N * N 체스판에 N개 배치했을 때 서로 공격할 수 없는 위치에 놓을 수 있는 방법의 경우의 수 확인
    • 퀸은 현재 자신 위치 기준 8방 직선방향으로 공격 가능
  • 완전탐색으로 풀 경우 O(N^N)를 가진다.
  • 백트래킹으로 접근하기
    • 상태 정의 : 각 행에 하나의 퀸이 위치하도록 한다.
    • 유망 함수 : 현재 행에 퀸을 놓았을 때, 다른 퀸과 충돌하는지 확인한다.
    • 해결책 확인 : 모든 퀸이 배치되었는지 확인
    • 재귀 호출 : 다음 행에 퀸을 배치
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    
      public class NQueens {
          // N Queen 문제: n x n 체스판에 n개의 퀸을 서로 공격하지 못하게 배치하는 문제
          private static final int N = 4; // 퀸의 개수
          private static int[] board = new int[N]; // 보드 배열, 인덱스는 행을, 값은 열을 나타냄
                                                                                              //
          // 보드의 현재 상태를 출력하는 함수
          private static void printBoard() {
              // 각 행을 반복
              for (int i = 0; i < N; ++i) {
                  // 각 열을 반복
                  for (int j = 0; j < N; ++j) {
                      // 현재 위치에 퀸이 있는지 확인
                      if (board[i] == j) {
                          System.out.print("Q "); // 퀸이 있으면 "Q" 출력
                      } else {
                          System.out.print(". "); // 퀸이 없으면 "." 출력
                      }
                  }
                  System.out.println(); // 한 행이 끝나면 줄바꿈
              }
              System.out.println(); // 보드 전체 출력 후 줄바꿈
          }
                                                                                              //
          // 현재 위치에 퀸을 놓아도 되는지 확인하는 유망함수
          private static boolean isSafe(int row, int col) {
              // 현재 행 이전의 모든 행을 검사
              for (int i = 0; i < row; ++i) {
                  // 1. 같은 열에 퀸이 있는지 확인
                  if (board[i] == col) {
                      return false; // 같은 열에 퀸이 있으면 false 반환
                  }
                  // 2. 같은 대각선에 퀸이 있는지 확인
                  if (Math.abs(board[i] - col) == Math.abs(i - row)) {
                      return false; // 같은 대각선에 퀸이 있으면 false 반환
                  }
              }
              return true; // 어떤 충돌도 없으면 true 반환
          }
                                                                                              //
          // N Queen 문제를 해결하기 위한 재귀 함수
          private static void solveNQueens(int row, int[] solutions) {
              // 모든 행에 퀸을 배치한 경우 (기저 조건)
              if (row == N) {
                  // 해결책을 찾았으므로 해결책 수 증가
                  solutions[0]++;
                  // 현재 보드 상태를 출력
                  printBoard();
                  return; // 함수 종료
              }
                                                                                              //
              // 현재 행의 모든 열에 대해 반복
              for (int col = 0; col < N; ++col) {
                  // 현재 위치에 퀸을 놓을 수 있는지 확인
                  if (isSafe(row, col)) {
                      board[row] = col; // 현재 행의 열에 퀸을 놓음
                      solveNQueens(row + 1, solutions); // 다음 행에 대해 재귀 호출
                      // 퀸을 제거할 필요 없음, 다음 반복에서 덮어쓰기 때문에
                  }
              }
          }
                                                                                              //
          public static void main(String[] args) {
              int[] solutions = {0}; // 해결책 수를 저장할 변수
                                                                                              //
              solveNQueens(0, solutions); // 첫 번째 행부터 시작
                                                                                              //
              System.out.println("총 해결책 수: " + solutions[0]); // 총 해결책 수 출력
          }
      }
    

피로도 문제 풀이

  • 피로도
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
      class Solution {
          private static int answer;
          private static int[][] Dungeons;
          private static boolean[] visited;
                                                                  //
          public int solution(int k, int[][] dungeons) {
              answer = 0;
              Dungeons = dungeons;
                                                                  //
              visited = new boolean[Dungeons.length];
                                                                  //
              back(k, 0);
                                                                  //
              return answer;
          }
                                                                  //
          private static void back(int k, int cnt) {
              for (int i = 0; i < Dungeons.length; i++) {
                                                                  //
                  if (!visited[i] && k >= Dungeons[i][0]) {
                      visited[i] = true;
                                                                  //
                      back(k - Dungeons[i][1], cnt + 1);
                      answer = Math.max(answer, cnt + 1);
                      visited[i] = false;
                  }
                                                                  //
              }
          }
      }
    
  • 못 풀었다. ↑ 풀이 ↑
  • 다른 사람 풀이를 보면 코드는 이해 할 수 있지만, 이 코드들이 왜 필요한 지 파악하는 것이 어렵다.
  • 이 풀이들이 내 것이 될 수 있도록 복습하고 계속 풀어 봐야겠다.

N-Queen 문제 풀이

  • N-Queen
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
      class Solution {
          private static int N;
          private static boolean[] width;
          private static boolean[] diagonal1;
          private static boolean[] diagonal2;
                                                              //
          public int solution(int n) {
              N = n;
              width = new boolean[n];
              diagonal1 = new boolean[n * 2];
              diagonal2 = new boolean[n * 2];
                                                              //
              int answer = getAns(0);
                                                              //
              return answer;
          }
                                                              //
          private static int getAns(int y) {
              int ans = 0;
                                                              //
              if (y == N) {
                  ans++;
              } else {
                  for (int i = 0; i < N; i++) {
                      if (width[i] || diagonal1[i + y] || diagonal2[i - y + N]) {
                          continue;
                      }
                                                                                          //
                      width[i] = diagonal1[i + y] = diagonal2[i - y + N] = true;
                                                                                          //
                      ans += getAns(y + 1);
                                                                                          //
                      width[i] = diagonal1[i + y] = diagonal2[i - y + N] = false;
                  }
              }
                                                                                          //
              return ans;
          }
      }
    
  • 못 풀었다. ↑ 풀이 ↑
  • 풀면 풀수록 어렵다 ㅠㅠ 언젠가는 혼자서 풀 수 있도록 반복 숙달 필수…

양궁 대회 문제 풀이

  • 양궁 대회
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    
      class Solution {
          private static int max;
          private static int[] answer;
          private static int[] apeach;
                                                              //
          private static int getScore(int[] ryan) {
              int score = 0;
                                                              //
              for (int i = 0; i <= 10; i++) {
                  if (ryan[i] + apeach[i] > 0) {
                      score += ryan[i] > apeach[i] ? (10 - i) : -(10 - i);
                  }
              }
                                                              //
              return score;
          }
                                                              //
          private static void calculateDiff(int[] ryan) {
              int score = getScore(ryan);
                                                              //
              if (max < score) {
                  max = score;
                  answer = ryan.clone();
              } else if (max > 0 && max == score) {
                                                              //
                  for (int i = 10; i >= 0; i--) {
                      if (answer[i] != ryan[i]) {
                          if (answer[i] < ryan[i]) {
                              answer = ryan.clone();
                          }
                          break;
                      }
                  }
              }
          }
                                                              //
          private static void backtrack(int n, int idx, int[] ryan) {
              if (n == 0) {
                  calculateDiff(ryan);
                  return;
              }
                                                                          //
              for (int i = idx; i <= 10; i++) {
                  int cnt = Math.min(n, apeach[i] + 1);
                  ryan[i] = cnt;
                  backtrack(n - cnt, i + 1, ryan);
                  ryan[i] = 0;
              }
          }
                                                                          //
          public static int[] solution(int n, int[] info) {
              apeach = info;
              max = 0;
              backtrack(n, 0, new int[11]);
                                                                          //
              return max == 0 ? new int[]{-1} : answer;
          }
      }
    
  • 못 풀었다. ↑ 풀이 ↑