2024-02-18
오늘은!
- 업랜디 한판 ✅ 2024-02-18
- 프로그래머스 DFS/BFS 퍼즐조각 문제 해결하기 ✅ 2024-02-18
- 이분탐색 단계 해결하기
- SQL 강의 듣기
오늘의 업랜디
2090 조화평균 (실패 후 해결 - 수학)
왜 자꾸 수학만 나옴? 버근가; ㅜㅜ
오늘의 프로그래머스
- 깊이/너비 우선 탐색(DFS/BFS)
퍼즐 조각 (Lv. 3)
퍼즐 조각 채우기
테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈 공간에 적절히 올려놓으려 합니다. 게임 보드와 테이블은 모두 각 칸이 1x1 크기인 정사각 격자 모양입니다. 이때, 다음 규칙에 따라 테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈칸에 채우면 됩니다.
- 조각은 한 번에 하나씩 채워 넣습니다.
- 조각을 회전시킬 수 있습니다.
- 조각을 뒤집을 수는 없습니다.
- 게임 보드에 새로 채워 넣은 퍼즐 조각과 인접한 칸이 비어있으면 안 됩니다.

퍼즐 조각 채우기 문제. 구현해야 할 부분이 많은 복잡한 로직의 문제다.
이렇게 구현할 부분이 많은 문제는 어느 하나의 로직이 조금이라도 정확하지 않으면 문제의 원인을 찾기가 많이 힘들어져서, 모든 기능을 모듈화하고 순차적으로 디버깅하며 풀어갔다.
좌표가 주어지지 않는다
이 문제에선 퍼즐이나 맵 정보의 좌표계가 주어지지 않고, 2D Vector에 0과 1로 표현된다.
따라서, 이 정보를 읽고 Point 구조체에 담아 배열로 넘겨주는 함수를 작성했다.
Queue로 BFS를 구현해 유효한 조각의 시작점에서 인접한 모든 조각을 구해서 좌표로 반환하게 하였다.
vector<vector<Point>> getCoordinateBFS(vector<vector<int>>& info, int selector) { // 셀렉터는 보드와 퍼즐을 구분하기 위한 매개변수
// BFS로 보드판의 빈칸 정보를 좌표계로 변환
queue<Point> q;
vector<Point> point_list;
vector<vector<Point>> puzzles;
int info_size = info.size();
vector<vector<bool>> visited(info_size, vector<bool>(info_size, false));
for (int y=0;y<info_size;++y) {
for (int x=0;x<info_size;++x) {
if (!visited[y][x] && info[y][x] == selector) { // 새로운 조각 발견
visited[y][x] = true;
q.push({x, y});
point_list.push_back({x, y});
while(!q.empty()) {
int cur_x = q.front().x;
int cur_y = q.front().y;
q.pop();
for (int i=0;i<4;++i) {
int next_x = cur_x + dx[i];
int next_y = cur_y + dy[i];
// Valid Check
if (next_x >= 0 && next_y >= 0 && next_x < info_size && next_y < info_size && !visited[next_y][next_x] && info[next_y][next_x] == selector) {
visited[next_y][next_x] = true;
q.push({next_x, next_y});
point_list.push_back({next_x, next_y});
}
}
}
// 조각 끝.
puzzles.push_back(point_list);
point_list.clear();
}
}
}
return puzzles;
}
퍼즐을 매치하려면 절대값이 필요하다
퍼즐 조각과 빈칸이 일치하는 지 확인하려면, 같은 크기와 모양에 대해서 같은 값을 가질 수 있게 해야 한다.
그래서 BFS로 도려낸 퍼즐 조각을 제일 좁게 가두는 직사각형(좌측 상단을 0, 0을 기준으로) 절댓값으로 바꿔야 한다.
이 모든 과정은 아래와 같은 자료구조를 선언해서 적절하게 담았다.
struct Point {
int x;
int y;
};
vector<vector<Point>> a;

조금 더 최적화 하기 위해선, 좌표값 등의 고유 정보를 해시로 변환하고 비교하는 방법이 있다.
(데이터셋이 작아서 이렇게까진 하지 않았고! 그냥 알아갔다.)
vector<vector<Point>> convertToAbs(vector<vector<Point>>& info) {
vector<vector<Point>> puzzles_abs;
for (const auto& puzzle : info) {
int abs_point_x = INT_MAX;
int abs_point_y = INT_MAX;
// 1. 현재 퍼즐의 기준점 값 찾기
for (const auto& point : puzzle) {
abs_point_x = min(abs_point_x, point.x);
abs_point_y = min(abs_point_y, point.y);
}
// 2. 기준점 값으로 모든 값을 absolute value로 변경
vector<Point> abs_point_list;
for (const auto& point : puzzle) {
abs_point_list.push_back({point.x - abs_point_x, point.y - abs_point_y});
}
puzzles_abs.push_back(abs_point_list);
abs_point_list.clear();
}
return puzzles_abs;
}
2D Vector을 90° 회전하기
2D Vector을 90° 회전하려면 아래와 같은 가정을 따른다. (물론, 간단하게 새 벡터를 만들고 90도로 돌린 값을 계산해서 넣어도 된다. 하지만 정사각형에만 적용 가능하고, 아래와 같은 방법을 따르는 것은 메모리 효율 이점이 있다)
- 전치 과정 (Transpose)
- 수평 뒤집기 (Horizontal Flip)
전치는 선형대수학에서 중요한 개념 중 하나이며, 행렬의 구조를 변경하거나 특정 행렬 연산을 수행할 때 자주 사용된다고 한다.
평소에 x, y 좌표를 헷갈려서 반대로 넣는 바보같은 행위를 자주 하는데. 전치라는 개념을 처음 들었을 때, "어! 이거 x좌표랑 y좌표 실수로 반대로 넣었을때의 효과랑 같네 ..?" 라고 생각했다.
실제로 같다.

전치란 좌표축을 따라 모든 값을 반사시키는 것이다. 그냥 간단히, x와 y좌표를 반대로 넣었을때의 효과다.
전치를 통해서 마치 거울처럼 좌표축을 따라 요소가 반사되게 할 수 있다. 그 다음에 수평 뒤집기를 수행하면, 각 행의 요소가 좌우로 뒤집히게 되어, 원래 행렬을 기준으로 시계 방향으로 90° 회전한 것과 같은 결과가 된다.
수평 뒤집기란 말 그대로 x축의 요소를 양쪽에서부터 뒤집는 것.
// 전치
void transpose(vector<vector<int>>& info) {
int n = info.size();
for (int i=0;i<n;++i) {
for (int j=i+1;j<n;++j) {
swap(info[i][j], info[j][i]);
}
}
}
// 수평 뒤집기
void horizontalFlip(vector<vector<int>>& info) {
int n = info.size();
for (int i=0;i<n;++i) {
for (int j=0;j<n/2;++j) {
swap(info[i][j], info[i][n - 1 - j]);
}
}
}
void rotate90Degrees(vector<vector<int>>& info) {
// 전치 후 수평 뒤집기
transpose(info);
horizontalFlip(info);
}
이렇게 복잡하고 세밀한 문제를 풀 때에는, 기능을 세분화해서 구현하고 관리하는 게 정신 건강에 이롭다.
또, for문을 여러번 돌리게 될 때에는. int n = info.size();
처럼 size를 미리 변수에 받아놓고 쓰는게 좋은 코딩 습관이다. (size가 도중에 바뀌지 않을때만)
이렇게 하지 않으면, 대규모 데이터셋에 대해서 매 반복마다 .size()를 호출해서 비효율을 낳는다.
퍼즐 일치 확인
절댓값으로 변환된 각 좌표 모음의 일치를 확인하기 위해 단순히 for문으로 좌표를 비교하려고 했지만, BFS 탐색의 특성 때문에 배열의 인덱스 순서가 같음을 보장할 수 없었다. 그리고, 퍼즐 조각이 차지하는 칸(크기)의 개수가 일치하지 않으면 결국에 맞지 않는 퍼즐이기 때문에. size가 다르면 바로 false를 반환해도 좋았다.
2중 포문을 돌리면서 두 벡터의 좌표 일치 개수를 세고, 이것이 총 size와 동일하다면 size를 반환했다.
int comparePuzzle(vector<Point>& a, vector<Point>& b) {
if (a.size() != b.size()) return 0;
int n = a.size();
int count = 0;
for (int i=0;i<n;++i) {
for (int j=0;j<n;++j) {
if (a[i].x == b[j].x && a[i].y == b[j].y) {
++count;
break;
}
}
}
return (count == n ? n : 0);
}
문제의 요구사항에서, 맞출 수 있는 퍼즐의 수가 아닌 맞출 수 있는 총 벡터 칸의 수를 요구하기 때문에 반환값을 int로 설정했다.
퍼즐의 중복 compare 방지
퍼즐을 compare하고 나서 조건에 모두 충족할 때, 퍼즐을 이미 사용했다고 표시하고. 현재의 자리에 다른 퍼즐이 끼워지지 않도록 표시하는 작업이 필요했다.
퍼즐은 visited vector을 만들어서 관리하고, 퍼즐 판의 빈칸은 메꾸는 방법을 사용했다.
퍼즐 판의 빈칸을 메꾸는 작업은 어려워 보였지만, BFS 탐색에서 얻어냈던 각 좌표계의 배열이, 절댓값 배열의 인덱스와 동일하기 때문에. compare에 성공한 인덱스의 좌표계 배열을 매개변수로 보내면 함수에서 해당 좌표의 값을 1로 바꾸는 작업을 통해 간단하게 구현했다.
// 보드를 채우는 함수 (중복 방지)
void fillBoard(vector<vector<int>>& game_board, vector<Point>& coordinates) {
for (const auto& co : coordinates) {
game_board[co.y][co.x] = 1;
}
}
비교 Solution
- 매개변수로 들어온 게임 보드 정보와 테이블 위에 놓여져있는 퍼즐 정보를 BFS를 이용해 좌표계로 변환했다.
- 변환된 좌표계를 다시 절대 좌표로 변환하고, 서로 다른 Vector에 담아줬다.
- 퍼즐의 중복 compare을 방지하기 위한 visited vector을 선언하고, 탐색 과정을 총 4번 반복한다.
- 각 빈칸에 대해, 방문되지 않은 모든 조각을 비교하며 구분한다.
- 각 반복에 대해, 보드판을 90도 돌리고 다시 좌표계 -> 절댓값 변환한다.
int solution(vector<vector<int>> game_board, vector<vector<int>> table) {
// 좌표계로 보드, 조각 변환
vector<vector<Point>> coordinated_table = getCoordinateBFS(table, 1);
vector<vector<Point>> coordinated_game_board = getCoordinateBFS(game_board, 0);
// 절대 좌표로 변환
vector<vector<Point>> puzzles = convertToAbs(coordinated_table);
vector<vector<Point>> current_game_board = convertToAbs(coordinated_game_board);
// 해당 퍼즐이 맞춰졌는지 확인
vector<bool> visited(puzzles.size(), false);
int puzzles_size = puzzles.size();
int answer = 0;
int loop = 3;
do {
int boards_size = current_game_board.size();
for (int i=0;i<boards_size;++i) {
for (int j=0;j<puzzles.size();++j) {
int cnt = comparePuzzle(puzzles[j], current_game_board[i]);
if (cnt && !visited[j]) { // 퍼즐이 조건을 충족하면
visited[j] = true; // 퍼즐을 완료로 표시, 보드를 메꾸기 (중복 방지)
fillBoard(game_board, coordinated_game_board[i]);
answer += cnt;
break;
}
}
}
// 90도 돌리기
rotate90Degrees(game_board);
coordinated_game_board.clear(); // 기존 벡터의 모든 요소 제거
coordinated_game_board = getCoordinateBFS(game_board, 0); // 새로운 값
current_game_board.clear();
current_game_board = convertToAbs(coordinated_game_board);
} while (loop--);
return answer;
}
모호한 부분
탐색 과정에서, 위의 코드와 같이 각 보드의 빈칸에 대해 모든 퍼즐을 반복하니까 AC를 받았고.
각 퍼즐 조각에 대해 모든 보드의 빈칸을 반복하며 탐색하니까 WA를 받았다.
내가 생각하기엔 둘의 차이가 없는데, 왜 전자만 AC를 받는지 모르겠다. 무언가 세밀한 상태 관리의 차이가 있는걸까? 계속 고민해보다가 답이 안나와서, 결국엔 해결하지 못했다. 나중에 깨닫게 되면 다시 글을 남겨야겠다.
<Wrong Code>
int boards_size = current_game_board.size();
for (int i=0;i<puzzles_size;++i) {
if (!visited[i]) { // 아직 해결 안된 퍼즐이여야만 탐색
for (int j=0;j<boards_size;++j) {
int cnt = comparePuzzle(puzzles[i], current_game_board[j]);
if (cnt) { // 퍼즐이 조건을 충족하면
visited[i] = true; // 퍼즐을 완료로 표시, 보드를 메꾸기 (중복 방지)
fillBoard(game_board, coordinated_game_board[j]);
answer += cnt;
break;
}
}
}
}
문제를 풀며 얻은 것들
해당 문제는 복잡한 문제로 세심한 접근이 필요했다.
Idea는 전체적으로 어렵지 않지만, 구현에서 약간의 문제를 겪었다.
- 벡터의 테두리를 채우고 시작하면 경계선 체크가 쉬워진다.
- 벡터가 50*50 이라면, 52*52를 선언하고 테두리를 그리라는 것. 약간의 효율은 포기한 간단하고 직관적인 방법인 거 같다.
- 맵이 작을 때는 Recursive DFS를 쓰자.
- N >= 1000 정도일 때는 그냥 BFS를 쓰고 (StackOverflow), 그 이하는 재귀 DFS를 쓰는게 직관적이고 간단하다. DFS를 사용할 수 있는 상황이라면 직관성을 택하자. 이번 문제에서 BFS Queue로 구현했는데 꽤 복잡했다.
- 퍼즐류는 좌표계를 (x, y) 대신 (r, c)를 쓰는 게 나은 것 같다.
- 이건 대체적으로 동의하는 부분. (x, y)의 개념이 너무 많은 부분에 통용되다 보니 전자가 X인지 후자가 X인지 헷갈릴 때가 너무 많고, 배열에 대해서도 직관적으로 다가간다면 첫번째 인덱스가 Row(행), 두번째 인덱스가 Column(열)을 나타내는 것이 일반적이다. (상상을 해보면, 2차원 벡터의 행이 낮아지면 높이가 낮아지는 것과 비슷하다) 그래서 배열을 나타낼때에는
vector[y][x];
를 쓰지만, 다른 구조체에 좌표를 저장할 때에는pair = {x, y};
로 하는것이 직관적인 것 같다. 이렇게 사람에 따라 보는 관점과 쓰임이 다르고, 나 조차도 생각이 계속 바뀌어서 혼란의 여지가 많다. 차라리 이런 문제에는 (Row, Column) 을 쓰자.vector[r][c]
로 선언하면 기존 수학의 좌표계와 달라서 헷갈릴 여지가 없다. - thanks for @readiz
최종 코드
#include <string>
#include <vector>
#include <utility>
#include <limits.h>
#include <queue>
#include <iostream>
#include <algorithm>
using namespace std;
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
struct Point {
int x;
int y;
};
int comparePuzzle(vector<Point>& a, vector<Point>& b) {
if (a.size() != b.size()) return 0;
int n = a.size();
int count = 0;
for (int i=0;i<n;++i) {
for (int j=0;j<n;++j) {
if (a[i].x == b[j].x && a[i].y == b[j].y) {
++count;
break;
}
}
}
return (count == n ? n : 0);
}
// 전치
void transpose(vector<vector<int>>& info) {
int n = info.size();
for (int i=0;i<n;++i) {
for (int j=i+1;j<n;++j) {
swap(info[i][j], info[j][i]);
}
}
}
// 수평 뒤집기
void horizontalFlip(vector<vector<int>>& info) {
int n = info.size();
for (int i=0;i<n;++i) {
for (int j=0;j<n/2;++j) {
swap(info[i][j], info[i][n - 1 - j]);
}
}
}
void rotate90Degrees(vector<vector<int>>& info) {
// 전치 후 수평 뒤집기
transpose(info);
horizontalFlip(info);
}
vector<vector<Point>> convertToAbs(vector<vector<Point>>& info) {
vector<vector<Point>> puzzles_abs;
for (const auto& puzzle : info) {
int abs_point_x = INT_MAX;
int abs_point_y = INT_MAX;
// 1. 현재 퍼즐의 기준점 값 찾기
for (const auto& point : puzzle) {
abs_point_x = min(abs_point_x, point.x);
abs_point_y = min(abs_point_y, point.y);
}
// 2. 기준점 값으로 모든 값을 absolute value로 변경
vector<Point> abs_point_list;
for (const auto& point : puzzle) {
abs_point_list.push_back({point.x - abs_point_x, point.y - abs_point_y});
}
puzzles_abs.push_back(abs_point_list);
abs_point_list.clear();
}
return puzzles_abs;
}
vector<vector<Point>> getCoordinateBFS(vector<vector<int>>& info, int selector) { // 셀렉터는 보드와 퍼즐을 구분하기 위한 매개변수
// BFS로 보드판의 빈칸 정보를 좌표계로 변환
queue<Point> q;
vector<Point> point_list;
vector<vector<Point>> puzzles;
int info_size = info.size();
vector<vector<bool>> visited(info_size, vector<bool>(info_size, false));
for (int y=0;y<info_size;++y) {
for (int x=0;x<info_size;++x) {
if (!visited[y][x] && info[y][x] == selector) { // 새로운 조각 발견
visited[y][x] = true;
q.push({x, y});
point_list.push_back({x, y});
while(!q.empty()) {
int cur_x = q.front().x;
int cur_y = q.front().y;
q.pop();
for (int i=0;i<4;++i) {
int next_x = cur_x + dx[i];
int next_y = cur_y + dy[i];
// Valid Check
if (next_x >= 0 && next_y >= 0 && next_x < info_size && next_y < info_size && !visited[next_y][next_x] && info[next_y][next_x] == selector) {
visited[next_y][next_x] = true;
q.push({next_x, next_y});
point_list.push_back({next_x, next_y});
}
}
}
// 조각 끝.
puzzles.push_back(point_list);
point_list.clear();
}
}
}
return puzzles;
}
// 보드를 채우는 함수 (중복 방지)
void fillBoard(vector<vector<int>>& game_board, vector<Point>& coordinates) {
for (const auto& co : coordinates) {
game_board[co.y][co.x] = 1;
}
}
int solution(vector<vector<int>> game_board, vector<vector<int>> table) {
// 좌표계로 보드, 조각 변환
vector<vector<Point>> coordinated_table = getCoordinateBFS(table, 1);
vector<vector<Point>> coordinated_game_board = getCoordinateBFS(game_board, 0);
// 절대 좌표로 변환
vector<vector<Point>> puzzles = convertToAbs(coordinated_table);
vector<vector<Point>> current_game_board = convertToAbs(coordinated_game_board);
// 해당 퍼즐이 맞춰졌는지 확인
vector<bool> visited(puzzles.size(), false);
int puzzles_size = puzzles.size();
int answer = 0;
int loop = 3;
do {
int boards_size = current_game_board.size();
for (int i=0;i<boards_size;++i) {
for (int j=0;j<puzzles.size();++j) {
int cnt = comparePuzzle(puzzles[j], current_game_board[i]);
if (cnt && !visited[j]) { // 퍼즐이 조건을 충족하면
visited[j] = true; // 퍼즐을 완료로 표시, 보드를 메꾸기 (중복 방지)
fillBoard(game_board, coordinated_game_board[i]);
answer += cnt;
break;
}
}
}
// 90도 돌리기
rotate90Degrees(game_board);
coordinated_game_board.clear(); // 기존 벡터의 모든 요소 제거
coordinated_game_board = getCoordinateBFS(game_board, 0); // 새로운 값
current_game_board.clear();
current_game_board = convertToAbs(coordinated_game_board);
} while (loop--);
return answer;
}
문제는 풀었지만 아직 이해할 수 없는 접근 방식의 오류가 있다.
나중에 이유를 깨닫게 된다면 다시 언급하겠다.
그리고, 일관성을 유지하는 것이 제일 중요하다
조화평균
조화평균을 구하는 문제다. 조화평균이라는 개념을 처음 들어봤다.
우리가 흔히 접하는 평균은 산술 평균인데, 조화 평균은 다른 상황에서의 평균을 내기 위한 .. 수학 ... 기법..? 인 것 같다.
각각의 수들을 뒤집어서(분모와 분자) 모두 더한 뒤, 그 값을 다시 뒤집으면 된다.
gcd를 이렇게 쉽게 구할 수 있었구나. 몰랐다.
아래 코드는 기존의 유클리드 호제법을 이용한 GCD를 계산하는 함수다.
int gcd(int a, int b) {
while (b != 0) {
int t = b;
b = a % b;
a = t;
}
return a;
}
근데 이렇게 간략하게도 구현할 수 있다.
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
lcm도 이런식으로 간략하게 구현할 수 있다
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
유클리드 호제법은 두 자연수의 최대공약수(Greatest Common Divisor, GCD)를 효율적으로 찾는 알고리즘으로, 핵심 원리는 두 자연수 a, b(단, a > b)의 최대공약수가 a를 b로 나눈 나머지 r과 b의 최대공약수와 같다는 것이다.
유클리드 호제법의 정확한 작동 구조/원리를 이해하긴 아직 힘들다. 하지만,, 외우면 되니까 ~! 내가 수학자도 아니고. 때론 효율적인 방법을 택하는 게 좋을때도 있는 것.
입력받은 모든 숫자를 분모로 해서, 최소공배수를 구한다. 그것이 분모가 된다.
분자는 분모를 그 숫자로 나눈 값을 더하면서 계산한다.
ll numerator = 0;
for (ll i = 0; i < N; ++i) {
numerator += denominator / A[i];
}
최종 코드
// 백준: 조화평균
// https://www.acmicpc.net/problem/2090
// 2024-02-18
#include <iostream>
#include <numeric>
using namespace std;
typedef long long ll;
// 연습 겸 구현
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }
int main() {
ll N;
cin >> N;
ll A[N];
ll denominator = 1; // 분모 초기화
for (ll i = 0; i < N; ++i) {
cin >> A[i];
// 최소공배수를 계산하여 분모 업데이트
denominator = lcm(denominator, A[i]);
}
ll numerator = 0;
for (ll i = 0; i < N; ++i) {
numerator += denominator / A[i];
}
int temp = gcd(denominator, numerator);
denominator /= temp;
numerator /= temp;
cout << denominator << "/" << numerator;
return 0;
}
오늘은 남은 시간을 모두 퍼즐 조각 채우기의 문제점을 찾는데에 썼다..
결국엔 찾지 못했고, 내일은 프로그래머스의 이분 탐색 문제와 SQL에 집중할 예정이다 ...
최종 평가
2024-02-18 학습 평가
오늘의 학습 성과:
- 업랜디 문제 2090 "조화평균" 해결
- 프로그래머스 DFS/BFS "퍼즐 조각 채우기" 문제 해결
- 이분탐색 및 SQL 강의 계획 수립(진행 상황 미보고)
학습 성취:
1. **조화평균 문제 해결**: 수학적 개념을 활용하여 문제를 해결한 점에서 뛰어난 학습 성과를 보였습니다. 특히, GCD와 LCM을 이용한 접근 방식은 효율적인 문제 해결 전략을 잘 보여주고 있습니다.
2. **퍼즐 조각 채우기 문제 해결**: 복잡한 로직과 다양한 기능 구현이 요구되는 문제를 해결하며, 세부적인 접근 방식과 모듈화를 통한 문제 해결 능력을 입증했습니다. BFS와 DFS를 활용한 탐색, 좌표계 변환, 퍼즐 매칭 로직 구현 등 다양한 알고리즘과 프로그래밍 기술을 적용한 점이 탁월한 성과로 평가됩니다.
3. **학습 계획 및 실행**: 업랜디 문제 해결과 프로그래머스 문제 해결을 통해 계획한 학습 목표를 달성했습니다. 하지만, 이분탐색 및 SQL 강의에 대한 구체적 진행 상황이 보고되지 않아, 이 부분은 아쉬운 점으로 남습니다.
평가 및 피드백:
오늘의 학습은 복잡한 알고리즘 문제를 해결하는 데 있어 뛰어난 논리적 사고와 프로그래밍 능력을 보여주었습니다. 특히, "퍼즐 조각 채우기" 문제 해결 과정에서 보인 체계적인 접근과 문제 분해 능력은 매우 인상적입니다.
다만, 이분탐색 및 SQL 강의 학습 계획에 대한 진행 상황이 불분명하여, 학습 계획의 전반적인 실행과 평가에 어려움이 있습니다. 앞으로 학습 계획을 세울 때는 학습 목표에 대한 진행 상황을 주기적으로 검토하고 보고하는 것이 중요합니다.
학습 성과 평가 점수: 85/100
- "퍼즐 조각 채우기" 문제 해결 과정에서 보여준 뛰어난 문제 해결 능력과 알고리즘 적용 능력을 높게 평가했습니다. 하지만, 계획된 학습 목표 중 일부가 보고되지 않아 점수가 감점되었습니다.
향후 개선 방안:
- 학습 계획을 수립할 때는 실행 가능한 범위와 자원을 고려하여 계획을 세우고, 학습 목표 달성에 대한 주기적인 검토와 조정이 필요합니다.
- 다양한 학습 목표를 설정할 때는 핵심적인 학습 내용에 집중하고, 계획한 학습 내용을 체계적으로 실행하여 학습 효과를 극대화할 수 있도록 합니다.
'일일 스터디노트' 카테고리의 다른 글
240220: SQL - GROUP BY, SUM 등 집계함수 KIT 풀이 끝. (0) | 2024.02.20 |
---|---|
240219: SQL SELECT 집중 학습, JOIN 이해, 최후의 승자 문제 (0) | 2024.02.19 |
240217: 손가락 세기 문제와 절댓값 좌표 변환 (0) | 2024.02.18 |
240216: 여행경로 문제 재귀 DFS 해결, 일정 (0) | 2024.02.16 |
240215: Recursive DFS와 Stack DFS의 중요한 차이점. 여행 경로 문제. (0) | 2024.02.15 |