diff --git "a/studies/week-02/yeseolee/26070-\353\263\264\354\204\235 \354\210\230\354\247\221 \353\241\234\353\264\207.md" "b/studies/week-02/yeseolee/26070-\353\263\264\354\204\235 \354\210\230\354\247\221 \353\241\234\353\264\207.md" new file mode 100644 index 0000000..565fc4b --- /dev/null +++ "b/studies/week-02/yeseolee/26070-\353\263\264\354\204\235 \354\210\230\354\247\221 \353\241\234\353\264\207.md" @@ -0,0 +1,85 @@ +# [SWEA 26070] 보석 수집 로봇 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AZwmBfua3q3HBIT3 | +| 📚 주제 | 0-1 BFS | +| 🎚️ 난이도 | D4 | +| ⏱️ 소요 시간 | 1H (실패) | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 + +* 최단 거리가 아닌 최소 회전 횟수를 구하는 문제 +* 이동에 따른 비용이 다름 -> 다익스트라 +* 가중치가 오직 0과 1 -> 다익스트라 대신 0-1 BFS + +## 2. 시간 복잡도 + +**O(N^2 * M)** — + +* (N * N 좌표) * (4 방향) * (M 목표물 개수) * (2 회전 여부 상태) + +## 3. 공간 복잡도 + +**O(N^2 * M)** — + +* 방문 처리를 위한 `visited` Set과 탐색을 위한 `deque`에 최대 상태 공간의 크기만큼 데이터가 적재 + +## 4. 핵심 풀이 방법 + +* **0-1 BFS:** `que.appendleft()`를 사용해 비용이 0인 직진을 덱의 최상단에 배치하고, `que.append()`를 사용해 비용이 1인 우회전을 뒤에 배치하여 탐색 우선순위를 보장 +* **상태 관리:** `(x, y, d, jewel, flag)`를 튜플로 묶어 방문 처리(`visited`)의 키로 사용 + +## 5. 실제 코드 + +```python +from collections import deque + + +def solution(n, m, board): + dx, dy = [0, 1, 0, -1], [1, 0, -1, 0] + visited = set() + que = deque([(0, 0, 0, 0, 1, False)]) + + while que: + x, y, d, rotate, jewel, flag = que.popleft() + + if board[x][y] == jewel: + jewel += 1 + flag = False + if jewel == m + 1: + return rotate + + state = (x, y, d, jewel, flag) + if state in visited: + continue + visited.add(state) + + # 직진 + nx, ny = x + dx[d], y + dy[d] + if 0 <= nx < n and 0 <= ny < n: + que.appendleft((nx, ny, d, rotate, jewel, False)) + + # 회전 + if not flag: + que.append((x, y, (d + 1) % 4, rotate + 1, jewel, True)) + + return -1 + + +T = int(input()) +for test_case in range(1, T + 1): + N = int(input()) + BOARD = [list(map(int, input().split())) for _ in range(N)] + M = max([max(row) for row in BOARD]) + answer = solution(N, M, BOARD) + print(f"#{test_case} {answer}") + +``` + +--- + +### 회고 (선택) diff --git "a/studies/week-02/yeseolee/26071-\353\270\224\353\241\235 \354\240\234\352\261\260 \352\262\214\354\236\204.md" "b/studies/week-02/yeseolee/26071-\353\270\224\353\241\235 \354\240\234\352\261\260 \352\262\214\354\236\204.md" new file mode 100644 index 0000000..00072d6 --- /dev/null +++ "b/studies/week-02/yeseolee/26071-\353\270\224\353\241\235 \354\240\234\352\261\260 \352\262\214\354\236\204.md" @@ -0,0 +1,144 @@ +# [SWEA 26071] 블록 제거 게임 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/userProblem/userProblemDetail.do?contestProbId=AZwmCVWq3uLHBIT3& | +| 📚 주제 | 메모이제이션 + 분할 정복 (DP) | +| 🎚️ 난이도 | D4 | +| ⏱️ 소요 시간 | 1h 30m (실패) | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 + +* 처음 코드는 `permutations`를 사용하여 블록을 깨는 모든 순서를 탐색하는 완전 탐색 방식으로 접근 +* 순열을 이용한 방식은 N이 커질 경우 탐색 범위가 기하급수적으로 늘어나 시간 초과가 발생 +* 최적화를 위해 메모이제이션 도입 -> 특정 구간 `[i, j]`에서 처음 깰 블록이 아닌, **가장 마지막에 깰 블록**을 `mid`로 설정 +* 이를 통해 남은 블록들이 서로 영향을 주지 않는 두 개의 독립적인 부분 문제(`[i, mid - 1]`와 `[mid + 1, j]`)로 나누는 분할 정복 방식으로 접근 + +## 2. 시간 복잡도 + +**O(N³)** — + +* DP 테이블의 상태(State) 개수는 `i`부터 `j`까지의 조합이므로 O(N²) +* 각 상태 `dp[i][j]`를 계산할 때, 분할점 `mid`를 `i`부터 `j`까지 순회하며 최댓값을 찾으므로 O(N)의 시간이 추가로 소요 +* 따라서 총 O(N²) * O(N) = O(N³) + +## 3. 공간 복잡도 + +**O(N²)** — + +* 계산된 구간 점수의 최댓값을 저장하기 위한 2차원 `dp` 배열의 크기가 N x N이므로 O(N²)의 공간이 필요 + +## 4. 핵심 풀이 방법 + +* **마지막에 터질 블록 기준:** 처음부터 어떤 블록을 터뜨릴지 고르면, 터진 블록으로 인해 인접 블록이 계속 변하여 부분 문제를 독립적으로 나눌 수 없음 +* **메모이제이션:** `dp[i][j]`를 `i`번째부터 `j`번째 블록까지 터뜨렸을 때 얻을 수 있는 최대 점수로 정의 + +## 5-1. 실제 코드 + +```python +def solution(n, arr): + dp = [[-1] * (n) for _ in range(n)] + + # 단일 블록 부수고 점수 획득 + def get_boundary_score(i, j, mid): + left, right = i - 1, j + 1 + if left >= 0 and right < n: + return arr[left] * arr[right] + elif left >= 0: + return arr[left] + elif right < n: + return arr[right] + else: + return arr[mid] + + # 구간 점수 최대값 (분할 정복) + def get_max_score(i, j): + if i > j: + return 0 + + if dp[i][j] != -1: + return dp[i][j] + + max_score = 0 + for mid in range(i, j + 1): + cur_score = ( + get_max_score(i, mid - 1) + + get_boundary_score(i, j, mid) + + get_max_score(mid + 1, j) + ) + max_score = max(max_score, cur_score) + + dp[i][j] = max_score + return max_score + + return get_max_score(0, n - 1) + +T = int(input()) +for test_case in range(1, T + 1): + N = int(input()) + K = list(map(int, input().split())) + answer = solution(N, K) + print(f"#{test_case} {answer}") + +``` + +## 5-2. 잘못된 코드 (테케만 경우 통과) +```python +def get_score(n,arr,order): + score = 0 + + for idx,mid in enumerate(order): + left,right = mid-1, mid+1 + while True: + if arr[left] == 0: + left -= 1 + continue + if arr[right] == 0: + right += 1 + continue + break + + if idx != len(order)-1: + score += arr[left] * arr[right] + arr[mid] = 0 + else: + case_1 = arr[left] * arr[right] + max(arr[left],arr[right]) *2 + case_2 = arr[mid] * 3 + score += max(case_1,case_2) + + return score + + +def solution(n, arr): + if n == 1: + return arr[0] + if n == 2: + return max(arr) * 2 + + max_score = 0 + ps = permutations(range(1,n-1), n-2) + + for p in ps: + score = get_score(n,arr[::],p) + max_score = max(max_score,score) + + return max_score + + + +T = int(input()) +for test_case in range(1, T + 1): + N = int(input()) + K = list(map(int,input().split())) + answer = solution(N,K) + print(f"#{test_case} {answer}") +``` + +--- + +### 회고 (선택) + +* "마지막에 수행할 작업"을 기준으로 나누는 구간 DP의 전형적인 테크닉(리트코드 'Burst Balloons' 유형)을 숙지해 두면 유사한 문제에 바로 적용할 수 있습니다. MM 민구님이 공유하신 링크 풀어보시면 좋습니다. \ No newline at end of file diff --git "a/studies/week-02/yeseolee/5566-\353\262\275\353\217\214 \352\271\250\352\270\260.md" "b/studies/week-02/yeseolee/5566-\353\262\275\353\217\214 \352\271\250\352\270\260.md" new file mode 100644 index 0000000..729beb7 --- /dev/null +++ "b/studies/week-02/yeseolee/5566-\353\262\275\353\217\214 \352\271\250\352\270\260.md" @@ -0,0 +1,125 @@ +# [SWEA 5566] 벽돌 깨기 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWXRQm6qfL0DFAUo | +| 📚 주제 | BFS + 백트래킹 | +| 🎚️ 난이도 | 상 | +| ⏱️ 소요 시간 | 1h 30m | +| 🔁 다시 풀기 | 예 | + +--- + +## 1. 접근 방법 + +* **배열 회전:** 2차원 배열의 중력 작용(빈 공간을 채우며 아래로 떨어지는 것)을 쉽게 처리하기 위해 보드를 시계 방향으로 90도 회전. 열이 행으로 변환되며, 단순히 0을 제외한 배열을 묶고 뒤에 0을 채우는 방식으로 중력을 구현 +* **연쇄 폭발 (BFS):** 벽돌이 깨질 때 주변 벽돌도 연쇄적으로 깨지므로, 이를 처리하기 위해 BFS 사용 +* **백트래킹:** 보드의 상태를 백업(`prev_board`)하고, 특정 열에 구슬을 떨어뜨린 뒤(BFS 및 중력 적용), 다음 재귀로 넘어가며 최댓값을 갱신 + +## 2. 시간 복잡도 + +**O(W^N * W * H)** — + +* 구슬을 떨어뜨릴 열을 선택하는 경우의 수는 최대 **W^N** +* 매 경우마다 연쇄 폭발을 처리하는 BFS와 중력 작용 함수가 실행되며, 이는 보드의 모든 칸을 한 번씩 순회하므로 **O(W * H)** + +## 3. 공간 복잡도 + +**O(N * W * H)** — + +* 백트래킹 재귀 호출 최대 **N** 깊이 +* 각 재귀 깊이마다 보드의 상태를 저장하기 위해 `prev_board` 배열(**W * H**)을 복사 +* BFS 탐색 시 사용되는 큐와 방문 처리용 집합(Set)이 최대 **W * H** + +## 4. 핵심 풀이 방법 ⭐ + +* **배열 90도 회전:** `board = [list(row) for row in zip(*board[::-1])]` 코드를 통해 2차원 배열을 시계 방향으로 회전 +* **중력 구현:** 리스트 컴프리헨션을 이용해 0이 아닌 값만 추출하고, 기존 배열의 길이에서 추출한 값의 길이를 뺀 만큼 0을 `extend` +* **가지치기:** 남은 횟수(`tries`)가 0이 되거나, 현재까지 깬 벽돌 수(`cnt`)가 전체 벽돌 수(`block_cnt`)와 동일해지면 조기 종료 +* **허공 처리:** 특정 열에 벽돌이 하나도 없는 경우(허공) 해당 열에서의 진행을 패스 + +## 5. 실제 코드 + +```python +from collections import deque + + +def solution(n, w, h, board): + # 시계 90도 회전 + board = [list(row) for row in zip(*board[::-1])] + block_cnt = sum([1 for i in range(w) for j in range(h) if board[i][j] != 0]) + max_break_cnt = 0 + dx, dy = [0, 0, 1, -1], [1, -1, 0, 0] + + def _blast(i, j): + nonlocal board + visited = set([(i, j)]) + que = deque([(i, j)]) + + while que: + x, y = que.popleft() + radius = board[x][y] + board[x][y] = 0 + + if radius <= 1: + continue + + for d in range(4): + for r in range(1, radius): + nx, ny = x + dx[d] * r, y + dy[d] * r + if not (0 <= nx < w and 0 <= ny < h): + continue + if (nx, ny) in visited: + continue + if board[nx][ny] == 0: + continue + + visited.add((nx, ny)) + que.append((nx, ny)) + + return len(visited) + + def _gravity(): + nonlocal board + for x in range(w): + arr = [board[x][y] for y in range(h) if board[x][y] != 0] + arr.extend([0] * (h - len(arr))) + board[x] = arr + + def _backtracking(tries, cnt): + nonlocal max_break_cnt, board + + if cnt == block_cnt or tries == 0: + max_break_cnt = max(max_break_cnt, cnt) + return + + for x in range(w): + for y in range(h - 1, -1, -1): + if board[x][y] != 0: + prev_board = [row[:] for row in board] + cur_cnt = _blast(x, y) + _gravity() + _backtracking(tries - 1, cnt + cur_cnt) + board = prev_board + break + else: + # 허공 + _backtracking(tries - 1, cnt) + + _backtracking(n, 0) + return block_cnt - max_break_cnt + + +T = int(input()) +for test_case in range(1, T + 1): + N, W, H = map(int, input().split()) + board = [list(map(int, input().split())) for _ in range(H)] + answer = solution(N, W, H, board) + print(f"#{test_case} {answer}") + +``` + +--- + +### 🧠 회고 (선택) +* 배열을 회전시키는 아이디어를 적용하면 중력 처리와 같은 특정 이동 로직이 쉬워집니다. diff --git "a/studies/week-02/yeseolee/5658-\353\263\264\353\254\274\354\203\201\354\236\220 \353\271\204\353\260\200\353\262\210\355\230\270.md" "b/studies/week-02/yeseolee/5658-\353\263\264\353\254\274\354\203\201\354\236\220 \353\271\204\353\260\200\353\262\210\355\230\270.md" new file mode 100644 index 0000000..3d6f367 --- /dev/null +++ "b/studies/week-02/yeseolee/5658-\353\263\264\353\254\274\354\203\201\354\236\220 \353\271\204\353\260\200\353\262\210\355\230\270.md" @@ -0,0 +1,78 @@ +# [SWEA 5658] 보물상자 비밀번호 + +| 항목 | 내용 | +|:---|:---| +| 🔗 문제 | https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AWXRUN9KfZ8DFAUo | +| 📚 주제 | 구현(시뮬레이션) | +| 🎚️ 난이도 | 중 | +| ⏱️ 소요 시간 | 30M | +| 🔁 다시 풀기 | 아니오 | + +--- + +## 1. 접근 방법 + +* 덱(deque)을 활용해 입력 배열을 회전시키며 가능한 모든 문자열 조합을 추출 +* 16진수 문자열을 10진수로 변환한 뒤 집합(set)에 저장하여 중복 제거 + +## 2. 시간 복잡도 + +**O(N²)** — + +* 바깥쪽 루프에서 상자를 회전하는 횟수는 `N // 4`번 +* 안쪽 루프에서 `deque`를 `list`로 형변환하고 슬라이싱하는 작업 O(N) +* 따라서 총 O(N²) + +## 3. 공간 복잡도 + +**O(N)** — + +* 길이 N의 입력을 저장하는 `deque`에 O(N) +* 중복 제거에 사용하는 `set`인 `hubo`에 최대 N개가 저장되므로 O(N) + +## 4. 핵심 풀이 방법 + +* 변의 길이(`N // 4`)를 기준으로 부분 문자열을 슬라이싱하여 분리하는 인덱스 계산 +* `deque.append(deque.popleft())`를 이용한 문자열 회전 로직 구현 +* 파이썬의 집합 자료구조 `set`을 사용하여 삽입과 동시에 중복 처리를 수행 + +## 5. 실제 코드 + +```python +from collections import deque + +def convert(str): + result = 0 + n = 1 + for ch in str[::-1]: + num = 10 + ord(ch) - ord("A") if (ord("A") <= ord(ch) <= ord("F")) else int(ch) + result += num * n + n *= 16 + return result + +def solution(n, k, arr): + # n: 8~28 정수 중 4의 배수 + # arr: 0이상 F이하 + # k번째 큰 수 10진수로 반환 + que = deque(arr) + hubo = set() + for _ in range(n // 4): + for i in range(4): + hubo.add(convert("".join(list(que)[(n // 4) * i : (n // 4) * (i + 1)]))) + que.append(que.popleft()) + return sorted(list(hubo), reverse=True)[k - 1] + +T = int(input()) +for test_case in range(1, T + 1): + N, K = map(int, input().split()) + ARR = list(input().strip()) + answer = solution(N, K, ARR) + print(f"#{test_case} {answer}") + +``` + +--- + +### 🧠 회고 (선택) + +* `convert` 함수를 직접 구현하여 16진수를 10진수로 변환했지만, 파이썬 내장 함수인 `int(문자열, 16)`을 사용하면 변환 로직을 완전히 생략하고 코드를 단축할 수 있었습니다... \ No newline at end of file