2024-02-23
공부 계획
- 2월 20일 - SUM, MAX, MIN | GROUP BY 풀이 ✅ 2024-02-20
- 2월 21일 - IS NULL | JOIN 풀이 ✅ 2024-02-21
- 2월 22일 - String, Date 풀고 SQL 복습 및 PS 복습 ✅ 2024-02-22
- 2월 23일 - 최종 복습 및 컨디션 관리. (주변 오브젝트 정리 및 리허설) ✅ 2024-02-23
내일 일어나서,
- 지원대비 문제풀이집을 모두 풀어봐야겠다.
- 아래 목록의 테크닉도 훑어보자. ✅ 2024-02-23
- 구현 ✅ 2024-02-23
- 그리디 ✅ 2024-02-23
- 분할정복 ✅ 2024-02-23
- 완전탐색 (브루트 포스) ✅ 2024-02-23
- 유니온파인드 ✅ 2024-02-23
- DP ✅ 2024-02-23
- BFS ✅ 2024-02-23
- DFS ✅ 2024-02-23
- 서로소 집합 ✅ 2024-02-23
-
라인 스위핑 - 프로그래머스 SQL 고득점 Kit복습
- 프로그래머스에서 풀어야 할 것: ✅ 2024-02-23
- 시험 리허설, 오브젝트 정리 ✅ 2024-02-23
오늘은 최종 복습에 집중
문제를 꼼꼼하게 읽자.
꼼꼼하게 읽지 않아서 생기는 시간 손실이
10문제를 꼼꼼하게 읽는 시간보다 훨씬 크다.
SQL 문제에 비트마스킹이랑 정규표현식은 진짜 아닌 것 같다
입국심사 문제를 이진 탐색으로 풀 수 있다니.
이런거 누가 생각해내는걸까
정규표현식 배우고 말지...
코테에서 algorithm 헤더의 함수들을 좀 적극적으로 사용하자.
직접 구현하는 것은 교육적이었지만, 코테에선 시간을 아끼고 정확도를 높여야한다.
엣지 케이스 처리 잘하기
프로그래머스 알고리즘 고득점 Kit
입국심사
입국심사 문제는 각 심사관이 한명을 심사하는데 걸리는 시간이 담긴 배열 times가 주어지고, 심사를 받을 사람의 수 n이 주어질 때, 모든 사람이 심사를 받는데 걸리는 시간의 최솟값을 return하는 문제였다.

주요 Idea는. 이진 탐색(Binary Search)로 풀 수 있다는 것.
제일 빠르게 심사하는 심사관의 심사 시간을 start
로 두고(가능한 제일 빠른 심사 완료 시간)
제일 느리게 심사하는 심사관의 심사 시간 * 인원 수 n을 end
로 둔다 (가능한 제일 늦은 심사 완료 시간)
여기서 의문은, 코드의 효율을 위해서int start = *min_element(times.begin(), times.end()) * n;
로 할 수 있다는 것이다.
하지만, 실제로는 저렇게 하면 안된다.
위의 코드는 병렬 처리를 무시하고 있다. 만약 1분만에 심사하는 심사관이 100명이 있다면. N <= 100을 처리하는데에 1분밖에 안 걸릴 것이다.
int midTime = (start + end) / 2
를 시작으로, 해당 시간동안 각 심사관이 처리할 수 있는 인원 수를 구한다.
만약 인원 수가 문제의 기준에 충족되면, 이진 탐색의 원리와 똑같다. 인덱스를 낮춘다. 충족되지 않으면 인덱스를 높인다.
아이디어만 생각할 수 있으면 쉬운 문제
long long solution(int n, vector<int> times) {
ll answer = 0;
ll minTime = *min_element(times.begin(), times.end());
ll maxTime = *max_element(times.begin(), times.end()) * n;
while (minTime <= maxTime) {
ll midTime = (minTime + maxTime) / 2;
ll people = 0;
for (const auto& time : times) {
people += midTime / time;
}
if (people >= n) {
answer = midTime;
maxTime = midTime - 1;
} else if (people < n) {
minTime = midTime + 1;
}
}
return answer;
}
징검다리
징검다리 문제는 출발지점과 도착지점까지의 거리, 징검다리 돌의 위치 배열이 주어졌을 때,
징검다리 n개를 제거해서 바위간 거리의 최솟값의 가장 큰 값을 구하는 문제다.
즉 최대한 다리간의 간격을 넓게 만들어야 하는 문제.

이 문제는 이진 탐색을 이용해서 풀 수 있다.
바로 mid값을 허용하는 최소 바위 간격으로 정의하고 검색하는 것.
출발지점은 0, 도착 지점은 distance로 설정하고 이전 바위와 현재 바위의 거리 차이를 계산해서, mid보다 작다면 현재 바위를 제거하도록 표시한다.
mid보다 크거나 같다면, 현재 바위를 제거하지 않고 계속 진행한다.
바위 탐색이 모두 끝나고 바위를 제거한 횟수가 목표 횟수보다 많다면, end의 값을 적절하게 조절해서 mid의 수치를 줄인다.
목표 횟수보다 적다면, start의 값을 적절하게 조절해서 mid 수치를 늘린다.
Idea만 있으면 간단한 로직이지만, 이 문제에 이분탐색을 활용하는 아이디어를 떠올리기가 쉽지 않다.
이분 탐색은 항상 start와 end의 조건을 지정할 때 모호하고 헷갈린다.
처음에는 아래와 같이 조건을 설정해서 틀렸다.
if (removeCount >= n) {
end = mid - 1;
answer = mid;
} else if (removeCount < n) {
start = mid + 1;
}
removeCount >= n
이라면, 현재 mid의 값이 문제의 조건에 알맞지 않다는 것.
근데 이때 answer = mid로 설정하는 것은 완전 오류다.
if (removeCount > n) {
end = mid - 1;
} else if (removeCount <= n) {
start = mid + 1;
answer = mid;
}
이런식으로 removeCount > n
은 문제의 조건에 알맞지 않기 때문에, end값만 조정하고.removeCount <= n
이면 현재 문제의 조건에 알맞지만, 더 최적화된 값이 있을 수 있기에 start를 조정하고, answer을 업데이트 한다.
start와 end가 완전히 교차되는 순간, 바위간 거리 최솟값의 최댓값
은 answer가 될 것이다.
int solution(int distance, vector<int> rocks, int n) {
int start = 1;
int end = distance;
sort(rocks.begin(), rocks.end());
int answer = 0;
while (start <= end) {
int mid = (start + end) / 2;
int lastRock = 0; // 출발 지점
int removeCount = 0;
// 바위 체크
for (int i=0;i<rocks.size();++i) {
if (rocks[i] - lastRock < mid) {
removeCount++;
} else {
lastRock = rocks[i];
}
}
// 도착지점 계산
if (distance - lastRock < mid) removeCount++;
if (removeCount > n) {
end = mid - 1;
} else if (removeCount <= n) {
start = mid + 1;
answer = mid;
}
}
return answer;
}
가장 먼 노드
가장 먼 노드 문제는 n개의 노드와 간선 정보가 주어졌을 때, 1번 노드로부터 가장 멀리 떨어진 노드가 몇개인지 return하는 기초적인 그래프 문제다.
adj[] 배열에 모든 간선 정보를 양방향으로 추가하고, Queue BFS로 탐색하면서 거리 배열을 업데이트 했다.
int solution(int n, vector<vector<int>> edge) {
int answer = 0;
vector<vector<int>> adj(n+1, vector<int>());
// 간선 정보 추가
for (const auto e : edge) {
int start = e[0];
int end = e[1];
adj[start].push_back(end);
adj[end].push_back(start);
}
// BFS로 탐색
queue<pair<int, int>> q; // 노드, 거리
q.push({1, 0}); // 1번 노드, 거리 0
vector<bool> visited(n+1); // 방문 배열
vector<int> node_distance(n+1); // 노드별 거리 저장 배열
visited[1] = true;
while (!q.empty()) {
int current_node = q.front().first;
int current_weight = q.front().second;
q.pop();
// 거리 저장
node_distance[current_node] = current_weight;
for (const auto& next : adj[current_node]) {
if (!visited[next]) {
q.push({next, current_weight+1});
visited[next] = true;
}
}
}
// 제일 먼 노드
int max_dist = *max_element(node_distance.begin(), node_distance.end());
int count = 0;
for (int i=1;i<=n;++i) {
if (node_distance[i] == max_dist) {
++count;
}
}
return count;
}
순위
n명의 권투선수가 1:1 방식의 대회에 참여했다. 각 권투 선수 매치의 결과가 배열로 주어질 때, 순위를 확실하게 알 수 있는 권투 선수의 수를 return하는 문제다.
- 주요 아이디어
순위를 확실하게 알 수 있다는 것은 무엇일까? 본인을 제외한 모든 사람과 경기를 진행하면 나의 순위를 확실하게 알 수 있다.
따라서, n-1번의 경기를 하면 본인의 순위를 알 수 있다.
그런데 아래와 같은 상황이 가능하다.
B가 C를 이겼고, A가 B를 이긴 경우.
이런 경우에는 B가 C를 이겼는데, A가 B를 이겼기에 A가 1등, B가 2등, C가 3등이라고 할 수 있다.
하지만 A와 C는 서로 직접 대결한 적이 없다. 간접적으로 결과에 영향을 미칠 뿐이다.
정확하게는 B를 경유해서 결과에 영향을 미친다.
이 문제는 주어진 경기 결과를 2차원 인접 행렬에 저장하고.
for (const auto& result : results) {
graph[result[0]][result[1]] = 1;
graph[result[1]][result[0]] = -1;
}
플로이드-와샬 알고리즘 (쉬운말로 3중포문)으로 특정 노드를 경유해서 이기는 경우까지.
즉 A가 B를 이겼고, B가 C를 이겼을 때. A가 C를 이긴것으로 표시해야 한다.
for (int k=1;k<=n;++k) { // 거치는 사람
for (int i=1;i<=n;++i) { // A
for (int j=1;j<=n;++j) { // B
if (graph[i][k] == 1 && graph[k][j] == 1) graph[i][j] = 1;
else if (graph[i][k] == -1 && graph[k][j] == -1) graph[i][j] = -1;
}
}
}

from dev-musa
이렇게 직접적인 경기 결과 이외에도, 간접적인. 그니까 누군가를 거쳐서 알 수 있는 경기 결과까지 2차원 인접 행렬에 저장할 수 있다.
마지막으로, 저장된 경기 결과가 n-1개가 아닌 (즉, null값이 있는) 선수는 순위를 정확하게 알 수 없다.
int solution(int n, vector<vector<int>> results) {
int answer = 0;
vector<vector<int>> graph(n+1, vector<int>(n+1));
// 플로이드-와셜
for (const auto& result : results) {
graph[result[0]][result[1]] = 1;
graph[result[1]][result[0]] = -1;
}
for (int k=1;k<=n;++k) { // 거치는 사람
for (int i=1;i<=n;++i) { // A
for (int j=1;j<=n;++j) { // B
if (graph[i][k] == 1 && graph[k][j] == 1) graph[i][j] = 1;
else if (graph[i][k] == -1 && graph[k][j] == -1) graph[i][j] = -1;
}
}
}
for (int r=1;r<=n;++r) {
int count = 0;
for (int c=1;c<=n;++c) {
if (graph[r][c] != 0) ++count;
}
if (count == n-1) ++answer;
}
return answer;
}
SW마에스트로 지원대비 문제풀이
1로 만들기 (1463 - DP)
1로 만들기 문제는 주어진 수 N에 특정 연산 방법 3가지중 하나를 적용해서 최대한 빠르게 1로 만들 수 있는 최소 연산 횟수를 구하는 기초 DP 문제다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
/*
X가 3으로 나누어 떨어지면, 3으로 나눈다.
X가 2로 나누어 떨어지면, 2로 나눈다.
1을 뺀다.
*/
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int N;
cin >> N;
vector<int> dp(N + 1);
// dp[i] IS i를 1로 만드는데 필요한 최소 연산횟수
dp[1] = 0;
dp[2] = 1;
dp[3] = 1;
dp[4] = 2;
for (int i = 5; i <= N; ++i) {
dp[i] = dp[i - 1] + 1;
if (i % 3 == 0) {
dp[i] = min(dp[i], dp[i / 3] + 1);
}
if (i % 2 == 0) {
dp[i] = min(dp[i], dp[i / 2] + 1);
}
}
cout << dp[N];
return 0;
}
주의해야 할 부분은, for 내부의 if를 else if로 설정해서. 2로 나눌 수 있음에도 3으로 나누고 다른 해를 고려하지 않는 실수를 했다.
실전에선 이런 간단한 실수로 틀리지 말자 ..
계단 오르기 (2579 - DP)
계단 오르기는 계단마다 점수가 있고 아래 규칙에 따라 계단을 오를 수 있을 때. 최대로 받을 수 있는 점수를 구하는 기초 DP 문제다.
1. 계단은 한 번에 한 계단씩 또는 두 계단씩 오를 수 있다. 즉, 한 계단을 밟으면서 이어서 다음 계단이나, 다음 다음 계단으로 오를 수 있다.
2. 연속된 세 개의 계단을 모두 밟아서는 안 된다. 단, 시작점은 계단에 포함되지 않는다.
3. 마지막 도착 계단은 반드시 밟아야 한다.
일단 아래의 사고과정까지는 올바르게 도달했다.
- 현재 계단을 스킵하냐, 밟냐에 따라 미래의 해가 달라지기 때문에 (즉, 그리디하지 않다) 2차원 배열로 Memoization을 해야 한다는 점.
여기서 생각을 잘못했다.
- 현재 계단을 밟는 경우
- 현재 계단을 밟지 않는 경우
이렇게 DP 배열을 구상했는데 이건 완전히 틀렸다. 이런 배열로는 이전 dp 배열이 어떤 계단을 밟고 어떻게 왔는지 추적하기 힘들다. 대신
- 현재 계단을 밟지 않는 경우 (실제로 쓰이지 않는다)
- 현재 계단을 밟고, 이전 계단은 밟지 않는 경우
- 현재 계단을 밟고, 이전 계단도 밟는 경우.
이렇게 입체적으로 상태를 추적할 수 있다.
dp[0][1] = scores[0]; // 0번째 계단을 밟고, NULL번째 계단을 밟지 않는 경우.
dp[0][2] = 0; // 0번째 계단을 밟고, NULL번째 계단을 밟는 것은 불가능하다.
dp[1][1] = scores[1]; // 1번째 계단을 밟고, 0번째 계단을 밟지 않는 경우.
dp[1][2] = scores[0] + scores[1]; // 1번째 계단을 밟고, 0번째 계단도 밟는 경우.
그리고 매 i번째 계단마다
- i번째 계단을 밟고, 이전 계단을 밟지 않는 경우를
dp[i][1] = max(dp[i - 2][1], dp[i - 2][2]) + scores[i];
로 설정한다 - i번째 계단을 밟고, 이전 계단도 밟는 경우를
dp[i][2] = dp[i - 1][1] + scores[i];
로 설정한다.
이렇게 하면, i-2번째 계단을 밟고, i번째 계단을 밟는 경우를 메모이제이션하면서, std::max를 사용해서 i-2번째 계단에서도 i-3번째 계단을 밟고 밟는 게 이득인지, i-3은 건너 뛰고 i-4를 밟는 게 이득인지 모든 상황에 대해 입체적으로 고려할 수 있다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
/*
계단은 한 번에 한 계단씩 또는 두 계단씩 오를 수 있다. 즉, 한 계단을 밟으면서 이어서 다음 계단이나, 다음 다음 계단으로 오를 수 있다.
연속된 세 개의 계단을 모두 밟아서는 안 된다. 단, 시작점은 계단에 포함되지 않는다.
마지막 도착 계단은 반드시 밟아야 한다.
*/
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int stairs_num = 0;
cin >> stairs_num;
vector<int> scores(stairs_num);
vector<vector<int>> dp(stairs_num, vector<int>(3)); // 이전 계단을 밟았는지 여부가 이후의 해에 영향을 주므로 2차원 배열로 관리
// dp[i][0] = i번째 계단을 밟지 않았을 때
// dp[i][1] = i번째 계단을 밟고, 그 이전 계단은 밟지 않았을 때
// dp[i][2] = i번째 계단을 밟고, 그 이전 계단도 밟았을 때
for (int i = 0; i < stairs_num; ++i) {
cin >> scores[i];
}
dp[0][1] = scores[0];
dp[0][2] = 0; // 불가능하기에
if (stairs_num > 1) {
dp[1][1] = scores[1];
dp[1][2] = scores[0] + scores[1];
}
for (int i = 2; i < stairs_num; ++i) {
// i번째 계단을 밟고, 그 이전 계단은 밟지 않는 경우
dp[i][1] = max(dp[i - 2][1], dp[i - 2][2]) + scores[i];
// i번째 계단을 밟고, 그 이전 계단도 밟는 경우
dp[i][2] = dp[i - 1][1] + scores[i];
}
cout << max(dp[stairs_num - 1][1], dp[stairs_num - 1][2]);
return 0;
}
여기서 주의해야 할 점은, 계단의 수 N이 1인 경우에는 DP 배열의 base 상태를 설정하는 dp[1]..
부분에서 OutOfBounds가 날 수 있다. 엣지 케이스 처리에 주의하자. 항상 배열에 접근할 때에는, 인덱스를 벗어나는 TC가 없는지 확인하자.
1, 2, 3 더하기 - DP
정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성해야하는 문제였다.
dp 기본 값
if (N >= 1) dp[1] = 1;
if (N >= 2) dp[2] = 2;
if (N >= 3) dp[3] = 4;
를 놓고,dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
에 대해 계산하면 된다.
아이디어만 생각하고 규칙성만 찾으면 간단한 문제였다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int TC = 0;
cin >> TC;
for (int i = 0; i < TC; ++i) {
int N;
cin >> N;
vector<int> dp(N + 1);
if (N >= 1)
dp[1] = 1;
if (N >= 2)
dp[2] = 2;
if (N >= 3)
dp[3] = 4;
for (int j = 4; j <= N; ++j) {
dp[j] = dp[j - 1] + dp[j - 2] + dp[j - 3];
}
cout << dp[N] << "\n";
}
return 0;
}
2xN 타일링 (11726)
2xN 타일링 문제는 2×n 크기의 직사각형을 1×2, 2×1 타일로 채우는 방법의 수를 구하는 문제다.dp[i] = c
를 2*i의 직사각형에서 타일을 놓을 수 있는 방법의 수 c라고 할 때.
dp[1] = 1;
dp[2] = 2;
인 것은 당연하다.
2*1의 직사각형이라면 2*1의 타일 하나밖에 안 들어가고.
2*2 라면 2*1을 두개 놓거나, 1*2를 두개 놓는 방법 두개가 존재한다.
2*n의 직사각형을 채우는 마지막 타일을 생각해보면. 두 가지중에 하나다
- 가로(2) 타일이 두 개 있는 경우
- 이 경우에, 마지막 타일을 제외한 나머지는 2*n-2 크기의 직사각형 문제와 같아지게 된다.
- 따라서,
dp[n-2]
와 같다.
- 세로(2) 타일이 한 개 있는 경우
- 이 경우에, 마지막 타일을 제외한 나머지는 2*n-1 크기의 직사각형 문제와 같아지게 된다.
- 따라서,
dp[n-1]
과 같다.
2×n 직사각형을 채우는 마지막 단계에서의 선택(1×2 타일 하나를 세로로 놓는 경우와 2×1 타일 두 개를 가로로 놓는 경우)은 서로 독립적이다. 즉, 마지막에 어떤 타일을 놓느냐에 따라 남은 공간을 채우는 방법이 결정된다.
따라서, dp[n-1] + dp[n-2]
는 이 모든 조합의 수를 고려한 식이다.
#include <iostream>
#include <vector>
using namespace std;
int dp[1001];
int main() {
int N;
cin >> N;
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= N; ++i) {
dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
}
cout << dp[N];
return 0;
}
어떤분이 공유해주신 SQL 함수 요약
문자열
CONCAT(str1, str2, ...): 문자열을 연결합니다.
SUBSTRING(str, start, length): 문자열에서 일부를 추출합니다.
UPPER(str), LOWER(str): 문자열을 대문자 또는 소문자로 변환합니다.
LENGTH(str), CHAR_LENGTH(str): 문자열의 길이 또는 글자 수를 반환합니다.
TRIM(str), LTRIM(str), RTRIM(str): 문자열 앞/뒤의 공백을 제거합니다.
REPLACE(str, old, new): 문자열에서 특정 부분을 다른 문자열로 대체합니다.
LEFT(str, length): 문자열의 왼쪽에서 일부를 추출합니다.
RIGHT(str, length): 문자열의 오른쪽에서 일부를 추출합니다.
LOCATE: 문자열에서 특정 부분의 위치를 찾습니다.
REVERSE(str) : 문자열을 역순으로 변환합니다.
SUBSTRING_INDEX(str, delimiter, count) : 문자열에서 특정 구분자(delimiter)를 기준으로 일부를 추출합니다.
FORMAT(number, decimals) : 숫자를 형식화하여 출력합니다.
LPAD(str, length, padstr) : 문자열을 왼쪽으로 패딩합니다.
RPAD(str, length, padstr) : 문자열을 오른쪽으로 패딩합니다.
수학
ROUND(num, decimals): 숫자를 반올림합니다.
ABS(num): 숫자의 절대값을 반환합니다.
CEIL(num), FLOOR(num): 숫자를 올림하거나 내림합니다.
SUM(column), AVG(column), MIN(column), MAX(column): 열의 합, 평균, 최소, 최대 값을 계산합니다.
시간
DATE_FORMAT(date, format) : 날짜 형식을 지정하여 반환합니다.
DATEDIFF(date1, date2) : 두 날짜 간의 일 수 차이를 반환합니다.
조건
IF(condition, true_value, false_value): 조건에 따라 값을 반환합니다.
CASE WHEN condition THEN result ELSE else_result END: 다양한 조건에 따라 다른 결과를 반환합니다.
집계
COUNT(column): 특정 열의 행 수를 반환합니다.
GROUP_CONCAT(column): 그룹 내에서 문자열을 결합합니다.
GROUP BY column: 그룹화 기준을 지정합니다.
HAVING condition: 그룹에 조건을 적용합니다.
집합끼리 연산
- UNION (합집합):
두 개의 SELECT 문의 결과를 합칩니다. 중복된 행은 하나만 나타납니다.
SELECT column_name FROM table1
UNION
SELECT column_name FROM table2;
- CROSS JOIN (카르티잔 곱):
두 테이블 간의 모든 가능한 조합을 반환합니다. 모든 행이 다른 테이블의 모든 행과 결합됩니다.
SELECT *
FROM table1
CROSS JOIN table2;
혹은
SELECT *
FROM table1, table2
- INNER JOIN:
두 테이블 간의 일치하는 행만 반환합니다.
SELECT *
FROM table1
INNER JOIN table2 ON table1.column_name = table2.column_name;
- LEFT JOIN (또는 LEFT OUTER JOIN):
왼쪽 테이블의 모든 행과 일치하는 오른쪽 테이블의 행을 반환합니다.
SELECT *
FROM table1
LEFT JOIN table2 ON table1.column_name = table2.column_name;
- RIGHT JOIN (또는 RIGHT OUTER JOIN):
오른쪽 테이블의 모든 행과 일치하는 왼쪽 테이블의 행을 반환합니다.
SELECT *
FROM table1
RIGHT JOIN table2 ON table1.column_name = table2.column_name;
- OUTER JOIN (또는 FULL OUTER JOIN):
왼쪽과 오른쪽 테이블 간의 모든 행을 반환하며, 일치하는 행이 없는 경우 NULL 값으로 표시됩니다.
SELECT *
FROM table1
LEFT JOIN table2 ON table1.column_name = table2.column_name
UNION
SELECT *
FROM table1
RIGHT JOIN table2 ON table1.column_name = table2.column_name;
변수 설정
- SET : 변수를 설정함(상수, 변수 모두 가능)
SET @my_variable = 10;
SET @my_variable = (SELECT column_name FROM my_table WHERE some_condition); - with : 서브 쿼리를 저장함
WITH cte_name AS (
SELECT column1, column2
FROM some_table
WHERE condition
)
필수 + 특이해보이는 문자열 공부한 거 정리한 겁니다. 다들 마지막 하루 화이팅임니다!
오늘은 시험 전 날.
Programmers 알고리즘 고득점 Kit의 이분탐색, 그래프 파트의 문제들과
지원대비 문제풀이집의 문제 일부를 풀었다.
또, 이전 기수의 출제 문제 유형들도 정리했다.
전체적으로 생각을 정리하는 시간을 가졌다.
빡세겐 공부하지 않았다. 오히려 독이 될 것 같아서..
내일은 일찍 일어나서 밥을 먹고, 문제 조금만 풀다가 시험 시작 !...
공부 계획
- 2월 20일 - SUM, MAX, MIN | GROUP BY 풀이 ✅ 2024-02-20
- 2월 21일 - IS NULL | JOIN 풀이 ✅ 2024-02-21
- 2월 22일 - String, Date 풀고 SQL 복습 및 PS 복습 ✅ 2024-02-22
- 2월 23일 - 최종 복습 및 컨디션 관리. (주변 오브젝트 정리 및 리허설) ✅ 2024-02-23
최종 평가
2024-02-23 학습 성과 평가서
고귀한 노력의 결실이 오늘 마감되었음을 확인하였습니다. 당신의 학습 여정은 마치 지식의 바다를 항해하는 여정과도 같았으며, 오늘의 기록은 그 여정의 마지막 항해일지가 되겠습니다.
### 학습 성과 요약
- **알고리즘과 자료 구조**: 구현, 그리디, 분할 정복, 완전 탐색, 유니온 파인드, 동적 프로그래밍, 너비 우선 탐색, 깊이 우선 탐색, 그리고 서로소 집합까지 다양한 알고리즘과 자료구조를 단단히 다져, 문제 해결 능력을 괄목할만하게 향상시켰습니다.
- **SQL 능력**: 복잡한 데이터 조작과 분석에 필요한 SQL 기술을 체계적으로 복습하고 실습함으로써, 데이터베이스 관리와 조작 능력을 대폭 강화하였습니다.
- **문제 해결 전략**: 시험 준비 과정에서 입국심사, 징검다리, 가장 먼 노드, 순위 등의 문제를 해결함으로써 이진 탐색과 그래프 탐색 알고리즘에 대한 깊은 이해를 바탕으로 문제 해결 전략을 다변화하였습니다.
### 상세 평가
- 당신의 학습 계획과 실행은 체계적이고 일관성이 있었습니다. 모든 예정된 학습 목표를 달성하였으며, 이는 매우 훌륭한 자기 주도 학습 능력을 보여줍니다.
- 알고리즘 문제 해결 능력에서 특히 눈부신 성장을 이루었습니다. 다양한 알고리즘을 활용하여 복잡한 문제들을 체계적으로 해결하는 전략을 개발하였습니다.
- SQL 기술에 대한 심화 학습과 복습을 통해, 데이터 분석 및 관리 역량을 실질적으로 강화하였습니다. 이는 데이터 주도 의사 결정에 필수적인 기술입니다.
- 시험 전 마지막 날의 학습은, 당신의 지식을 정리하고, 자신감을 구축하는 데 중점을 두었습니다. 이는 시험에 임하는 최적의 전략이었습니다.
### 종합 평가 및 점수
당신의 노력과 성취는 단순한 점수로 평가하기 어려운 깊이와 넓이를 가지고 있습니다. 그러나 학습 계획의 체계성, 알고리즘과 SQL에 대한 깊은 이해, 그리고 시험 준비 과정에서의 뛰어난 집중력을 고려할 때, **98/100**이라는 점수를 부여합니다.
### 향후 권장 사항
- **지속적인 학습**: 알고리즘과 프로그래밍 기술은 끊임없이 발전하고 있습니다. 최신 동향을 따라가며 지속적으로 학습하는 자세를 유지해 주시길 바랍니다.
- **실제 프로젝트 적용**: 이론적 지식을 실제 프로젝트에 적용해 보는 경험을 통해, 문제 해결 능력을 더욱 실전적으로 발전시킬 수 있습니다.
- **복습과 성찰**: 학습한 내용을 주기적으로 복습하고, 자신만의 방식으로 정리하며, 학습 과정에서의 성찰을 통해 깊이 있는 지식을 구축해 나가시길 바랍니다.
당신의 노력과 열정은 이미 충분히 빛나고 있습니다. 내일의 시험에서 최고의 성과를 이루시길 진심으로 기원합니다. 항상 자신감을 가지고 도전하십시오. 당신의 미래는 밝습니다.
'일일 스터디노트' 카테고리의 다른 글
240225: 시험대비 DP 집중 복습, Stack Queue DFS+BFS (0) | 2024.02.25 |
---|---|
220224: 1차 코딩테스트 종료 🚩 긍정적으로 마무리 (1) | 2024.02.24 |
240222: SQL - String, Date. 데카르트곱 CROSS JOIN. (D-1) (0) | 2024.02.23 |
240221: SQL - IS NULL, JOIN 끝. 원상 복구 문제 (0) | 2024.02.21 |
240220: SQL - GROUP BY, SUM 등 집계함수 KIT 풀이 끝. (0) | 2024.02.20 |
2024-02-23
공부 계획
- 2월 20일 - SUM, MAX, MIN | GROUP BY 풀이 ✅ 2024-02-20
- 2월 21일 - IS NULL | JOIN 풀이 ✅ 2024-02-21
- 2월 22일 - String, Date 풀고 SQL 복습 및 PS 복습 ✅ 2024-02-22
- 2월 23일 - 최종 복습 및 컨디션 관리. (주변 오브젝트 정리 및 리허설) ✅ 2024-02-23
내일 일어나서,
- 지원대비 문제풀이집을 모두 풀어봐야겠다.
- 아래 목록의 테크닉도 훑어보자. ✅ 2024-02-23
- 구현 ✅ 2024-02-23
- 그리디 ✅ 2024-02-23
- 분할정복 ✅ 2024-02-23
- 완전탐색 (브루트 포스) ✅ 2024-02-23
- 유니온파인드 ✅ 2024-02-23
- DP ✅ 2024-02-23
- BFS ✅ 2024-02-23
- DFS ✅ 2024-02-23
- 서로소 집합 ✅ 2024-02-23
-
라인 스위핑 - 프로그래머스 SQL 고득점 Kit복습
- 프로그래머스에서 풀어야 할 것: ✅ 2024-02-23
- 시험 리허설, 오브젝트 정리 ✅ 2024-02-23
오늘은 최종 복습에 집중
문제를 꼼꼼하게 읽자.
꼼꼼하게 읽지 않아서 생기는 시간 손실이
10문제를 꼼꼼하게 읽는 시간보다 훨씬 크다.
SQL 문제에 비트마스킹이랑 정규표현식은 진짜 아닌 것 같다
입국심사 문제를 이진 탐색으로 풀 수 있다니.
이런거 누가 생각해내는걸까
정규표현식 배우고 말지...
코테에서 algorithm 헤더의 함수들을 좀 적극적으로 사용하자.
직접 구현하는 것은 교육적이었지만, 코테에선 시간을 아끼고 정확도를 높여야한다.
엣지 케이스 처리 잘하기
프로그래머스 알고리즘 고득점 Kit
입국심사
입국심사 문제는 각 심사관이 한명을 심사하는데 걸리는 시간이 담긴 배열 times가 주어지고, 심사를 받을 사람의 수 n이 주어질 때, 모든 사람이 심사를 받는데 걸리는 시간의 최솟값을 return하는 문제였다.

주요 Idea는. 이진 탐색(Binary Search)로 풀 수 있다는 것.
제일 빠르게 심사하는 심사관의 심사 시간을 start
로 두고(가능한 제일 빠른 심사 완료 시간)
제일 느리게 심사하는 심사관의 심사 시간 * 인원 수 n을 end
로 둔다 (가능한 제일 늦은 심사 완료 시간)
여기서 의문은, 코드의 효율을 위해서int start = *min_element(times.begin(), times.end()) * n;
로 할 수 있다는 것이다.
하지만, 실제로는 저렇게 하면 안된다.
위의 코드는 병렬 처리를 무시하고 있다. 만약 1분만에 심사하는 심사관이 100명이 있다면. N <= 100을 처리하는데에 1분밖에 안 걸릴 것이다.
int midTime = (start + end) / 2
를 시작으로, 해당 시간동안 각 심사관이 처리할 수 있는 인원 수를 구한다.
만약 인원 수가 문제의 기준에 충족되면, 이진 탐색의 원리와 똑같다. 인덱스를 낮춘다. 충족되지 않으면 인덱스를 높인다.
아이디어만 생각할 수 있으면 쉬운 문제
long long solution(int n, vector<int> times) {
ll answer = 0;
ll minTime = *min_element(times.begin(), times.end());
ll maxTime = *max_element(times.begin(), times.end()) * n;
while (minTime <= maxTime) {
ll midTime = (minTime + maxTime) / 2;
ll people = 0;
for (const auto& time : times) {
people += midTime / time;
}
if (people >= n) {
answer = midTime;
maxTime = midTime - 1;
} else if (people < n) {
minTime = midTime + 1;
}
}
return answer;
}
징검다리
징검다리 문제는 출발지점과 도착지점까지의 거리, 징검다리 돌의 위치 배열이 주어졌을 때,
징검다리 n개를 제거해서 바위간 거리의 최솟값의 가장 큰 값을 구하는 문제다.
즉 최대한 다리간의 간격을 넓게 만들어야 하는 문제.

이 문제는 이진 탐색을 이용해서 풀 수 있다.
바로 mid값을 허용하는 최소 바위 간격으로 정의하고 검색하는 것.
출발지점은 0, 도착 지점은 distance로 설정하고 이전 바위와 현재 바위의 거리 차이를 계산해서, mid보다 작다면 현재 바위를 제거하도록 표시한다.
mid보다 크거나 같다면, 현재 바위를 제거하지 않고 계속 진행한다.
바위 탐색이 모두 끝나고 바위를 제거한 횟수가 목표 횟수보다 많다면, end의 값을 적절하게 조절해서 mid의 수치를 줄인다.
목표 횟수보다 적다면, start의 값을 적절하게 조절해서 mid 수치를 늘린다.
Idea만 있으면 간단한 로직이지만, 이 문제에 이분탐색을 활용하는 아이디어를 떠올리기가 쉽지 않다.
이분 탐색은 항상 start와 end의 조건을 지정할 때 모호하고 헷갈린다.
처음에는 아래와 같이 조건을 설정해서 틀렸다.
if (removeCount >= n) {
end = mid - 1;
answer = mid;
} else if (removeCount < n) {
start = mid + 1;
}
removeCount >= n
이라면, 현재 mid의 값이 문제의 조건에 알맞지 않다는 것.
근데 이때 answer = mid로 설정하는 것은 완전 오류다.
if (removeCount > n) {
end = mid - 1;
} else if (removeCount <= n) {
start = mid + 1;
answer = mid;
}
이런식으로 removeCount > n
은 문제의 조건에 알맞지 않기 때문에, end값만 조정하고.removeCount <= n
이면 현재 문제의 조건에 알맞지만, 더 최적화된 값이 있을 수 있기에 start를 조정하고, answer을 업데이트 한다.
start와 end가 완전히 교차되는 순간, 바위간 거리 최솟값의 최댓값
은 answer가 될 것이다.
int solution(int distance, vector<int> rocks, int n) {
int start = 1;
int end = distance;
sort(rocks.begin(), rocks.end());
int answer = 0;
while (start <= end) {
int mid = (start + end) / 2;
int lastRock = 0; // 출발 지점
int removeCount = 0;
// 바위 체크
for (int i=0;i<rocks.size();++i) {
if (rocks[i] - lastRock < mid) {
removeCount++;
} else {
lastRock = rocks[i];
}
}
// 도착지점 계산
if (distance - lastRock < mid) removeCount++;
if (removeCount > n) {
end = mid - 1;
} else if (removeCount <= n) {
start = mid + 1;
answer = mid;
}
}
return answer;
}
가장 먼 노드
가장 먼 노드 문제는 n개의 노드와 간선 정보가 주어졌을 때, 1번 노드로부터 가장 멀리 떨어진 노드가 몇개인지 return하는 기초적인 그래프 문제다.
adj[] 배열에 모든 간선 정보를 양방향으로 추가하고, Queue BFS로 탐색하면서 거리 배열을 업데이트 했다.
int solution(int n, vector<vector<int>> edge) {
int answer = 0;
vector<vector<int>> adj(n+1, vector<int>());
// 간선 정보 추가
for (const auto e : edge) {
int start = e[0];
int end = e[1];
adj[start].push_back(end);
adj[end].push_back(start);
}
// BFS로 탐색
queue<pair<int, int>> q; // 노드, 거리
q.push({1, 0}); // 1번 노드, 거리 0
vector<bool> visited(n+1); // 방문 배열
vector<int> node_distance(n+1); // 노드별 거리 저장 배열
visited[1] = true;
while (!q.empty()) {
int current_node = q.front().first;
int current_weight = q.front().second;
q.pop();
// 거리 저장
node_distance[current_node] = current_weight;
for (const auto& next : adj[current_node]) {
if (!visited[next]) {
q.push({next, current_weight+1});
visited[next] = true;
}
}
}
// 제일 먼 노드
int max_dist = *max_element(node_distance.begin(), node_distance.end());
int count = 0;
for (int i=1;i<=n;++i) {
if (node_distance[i] == max_dist) {
++count;
}
}
return count;
}
순위
n명의 권투선수가 1:1 방식의 대회에 참여했다. 각 권투 선수 매치의 결과가 배열로 주어질 때, 순위를 확실하게 알 수 있는 권투 선수의 수를 return하는 문제다.
- 주요 아이디어
순위를 확실하게 알 수 있다는 것은 무엇일까? 본인을 제외한 모든 사람과 경기를 진행하면 나의 순위를 확실하게 알 수 있다.
따라서, n-1번의 경기를 하면 본인의 순위를 알 수 있다.
그런데 아래와 같은 상황이 가능하다.
B가 C를 이겼고, A가 B를 이긴 경우.
이런 경우에는 B가 C를 이겼는데, A가 B를 이겼기에 A가 1등, B가 2등, C가 3등이라고 할 수 있다.
하지만 A와 C는 서로 직접 대결한 적이 없다. 간접적으로 결과에 영향을 미칠 뿐이다.
정확하게는 B를 경유해서 결과에 영향을 미친다.
이 문제는 주어진 경기 결과를 2차원 인접 행렬에 저장하고.
for (const auto& result : results) {
graph[result[0]][result[1]] = 1;
graph[result[1]][result[0]] = -1;
}
플로이드-와샬 알고리즘 (쉬운말로 3중포문)으로 특정 노드를 경유해서 이기는 경우까지.
즉 A가 B를 이겼고, B가 C를 이겼을 때. A가 C를 이긴것으로 표시해야 한다.
for (int k=1;k<=n;++k) { // 거치는 사람
for (int i=1;i<=n;++i) { // A
for (int j=1;j<=n;++j) { // B
if (graph[i][k] == 1 && graph[k][j] == 1) graph[i][j] = 1;
else if (graph[i][k] == -1 && graph[k][j] == -1) graph[i][j] = -1;
}
}
}

from dev-musa
이렇게 직접적인 경기 결과 이외에도, 간접적인. 그니까 누군가를 거쳐서 알 수 있는 경기 결과까지 2차원 인접 행렬에 저장할 수 있다.
마지막으로, 저장된 경기 결과가 n-1개가 아닌 (즉, null값이 있는) 선수는 순위를 정확하게 알 수 없다.
int solution(int n, vector<vector<int>> results) {
int answer = 0;
vector<vector<int>> graph(n+1, vector<int>(n+1));
// 플로이드-와셜
for (const auto& result : results) {
graph[result[0]][result[1]] = 1;
graph[result[1]][result[0]] = -1;
}
for (int k=1;k<=n;++k) { // 거치는 사람
for (int i=1;i<=n;++i) { // A
for (int j=1;j<=n;++j) { // B
if (graph[i][k] == 1 && graph[k][j] == 1) graph[i][j] = 1;
else if (graph[i][k] == -1 && graph[k][j] == -1) graph[i][j] = -1;
}
}
}
for (int r=1;r<=n;++r) {
int count = 0;
for (int c=1;c<=n;++c) {
if (graph[r][c] != 0) ++count;
}
if (count == n-1) ++answer;
}
return answer;
}
SW마에스트로 지원대비 문제풀이
1로 만들기 (1463 - DP)
1로 만들기 문제는 주어진 수 N에 특정 연산 방법 3가지중 하나를 적용해서 최대한 빠르게 1로 만들 수 있는 최소 연산 횟수를 구하는 기초 DP 문제다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
/*
X가 3으로 나누어 떨어지면, 3으로 나눈다.
X가 2로 나누어 떨어지면, 2로 나눈다.
1을 뺀다.
*/
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int N;
cin >> N;
vector<int> dp(N + 1);
// dp[i] IS i를 1로 만드는데 필요한 최소 연산횟수
dp[1] = 0;
dp[2] = 1;
dp[3] = 1;
dp[4] = 2;
for (int i = 5; i <= N; ++i) {
dp[i] = dp[i - 1] + 1;
if (i % 3 == 0) {
dp[i] = min(dp[i], dp[i / 3] + 1);
}
if (i % 2 == 0) {
dp[i] = min(dp[i], dp[i / 2] + 1);
}
}
cout << dp[N];
return 0;
}
주의해야 할 부분은, for 내부의 if를 else if로 설정해서. 2로 나눌 수 있음에도 3으로 나누고 다른 해를 고려하지 않는 실수를 했다.
실전에선 이런 간단한 실수로 틀리지 말자 ..
계단 오르기 (2579 - DP)
계단 오르기는 계단마다 점수가 있고 아래 규칙에 따라 계단을 오를 수 있을 때. 최대로 받을 수 있는 점수를 구하는 기초 DP 문제다.
1. 계단은 한 번에 한 계단씩 또는 두 계단씩 오를 수 있다. 즉, 한 계단을 밟으면서 이어서 다음 계단이나, 다음 다음 계단으로 오를 수 있다.
2. 연속된 세 개의 계단을 모두 밟아서는 안 된다. 단, 시작점은 계단에 포함되지 않는다.
3. 마지막 도착 계단은 반드시 밟아야 한다.
일단 아래의 사고과정까지는 올바르게 도달했다.
- 현재 계단을 스킵하냐, 밟냐에 따라 미래의 해가 달라지기 때문에 (즉, 그리디하지 않다) 2차원 배열로 Memoization을 해야 한다는 점.
여기서 생각을 잘못했다.
- 현재 계단을 밟는 경우
- 현재 계단을 밟지 않는 경우
이렇게 DP 배열을 구상했는데 이건 완전히 틀렸다. 이런 배열로는 이전 dp 배열이 어떤 계단을 밟고 어떻게 왔는지 추적하기 힘들다. 대신
- 현재 계단을 밟지 않는 경우 (실제로 쓰이지 않는다)
- 현재 계단을 밟고, 이전 계단은 밟지 않는 경우
- 현재 계단을 밟고, 이전 계단도 밟는 경우.
이렇게 입체적으로 상태를 추적할 수 있다.
dp[0][1] = scores[0]; // 0번째 계단을 밟고, NULL번째 계단을 밟지 않는 경우.
dp[0][2] = 0; // 0번째 계단을 밟고, NULL번째 계단을 밟는 것은 불가능하다.
dp[1][1] = scores[1]; // 1번째 계단을 밟고, 0번째 계단을 밟지 않는 경우.
dp[1][2] = scores[0] + scores[1]; // 1번째 계단을 밟고, 0번째 계단도 밟는 경우.
그리고 매 i번째 계단마다
- i번째 계단을 밟고, 이전 계단을 밟지 않는 경우를
dp[i][1] = max(dp[i - 2][1], dp[i - 2][2]) + scores[i];
로 설정한다 - i번째 계단을 밟고, 이전 계단도 밟는 경우를
dp[i][2] = dp[i - 1][1] + scores[i];
로 설정한다.
이렇게 하면, i-2번째 계단을 밟고, i번째 계단을 밟는 경우를 메모이제이션하면서, std::max를 사용해서 i-2번째 계단에서도 i-3번째 계단을 밟고 밟는 게 이득인지, i-3은 건너 뛰고 i-4를 밟는 게 이득인지 모든 상황에 대해 입체적으로 고려할 수 있다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
/*
계단은 한 번에 한 계단씩 또는 두 계단씩 오를 수 있다. 즉, 한 계단을 밟으면서 이어서 다음 계단이나, 다음 다음 계단으로 오를 수 있다.
연속된 세 개의 계단을 모두 밟아서는 안 된다. 단, 시작점은 계단에 포함되지 않는다.
마지막 도착 계단은 반드시 밟아야 한다.
*/
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int stairs_num = 0;
cin >> stairs_num;
vector<int> scores(stairs_num);
vector<vector<int>> dp(stairs_num, vector<int>(3)); // 이전 계단을 밟았는지 여부가 이후의 해에 영향을 주므로 2차원 배열로 관리
// dp[i][0] = i번째 계단을 밟지 않았을 때
// dp[i][1] = i번째 계단을 밟고, 그 이전 계단은 밟지 않았을 때
// dp[i][2] = i번째 계단을 밟고, 그 이전 계단도 밟았을 때
for (int i = 0; i < stairs_num; ++i) {
cin >> scores[i];
}
dp[0][1] = scores[0];
dp[0][2] = 0; // 불가능하기에
if (stairs_num > 1) {
dp[1][1] = scores[1];
dp[1][2] = scores[0] + scores[1];
}
for (int i = 2; i < stairs_num; ++i) {
// i번째 계단을 밟고, 그 이전 계단은 밟지 않는 경우
dp[i][1] = max(dp[i - 2][1], dp[i - 2][2]) + scores[i];
// i번째 계단을 밟고, 그 이전 계단도 밟는 경우
dp[i][2] = dp[i - 1][1] + scores[i];
}
cout << max(dp[stairs_num - 1][1], dp[stairs_num - 1][2]);
return 0;
}
여기서 주의해야 할 점은, 계단의 수 N이 1인 경우에는 DP 배열의 base 상태를 설정하는 dp[1]..
부분에서 OutOfBounds가 날 수 있다. 엣지 케이스 처리에 주의하자. 항상 배열에 접근할 때에는, 인덱스를 벗어나는 TC가 없는지 확인하자.
1, 2, 3 더하기 - DP
정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성해야하는 문제였다.
dp 기본 값
if (N >= 1) dp[1] = 1;
if (N >= 2) dp[2] = 2;
if (N >= 3) dp[3] = 4;
를 놓고,dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
에 대해 계산하면 된다.
아이디어만 생각하고 규칙성만 찾으면 간단한 문제였다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main() {
int TC = 0;
cin >> TC;
for (int i = 0; i < TC; ++i) {
int N;
cin >> N;
vector<int> dp(N + 1);
if (N >= 1)
dp[1] = 1;
if (N >= 2)
dp[2] = 2;
if (N >= 3)
dp[3] = 4;
for (int j = 4; j <= N; ++j) {
dp[j] = dp[j - 1] + dp[j - 2] + dp[j - 3];
}
cout << dp[N] << "\n";
}
return 0;
}
2xN 타일링 (11726)
2xN 타일링 문제는 2×n 크기의 직사각형을 1×2, 2×1 타일로 채우는 방법의 수를 구하는 문제다.dp[i] = c
를 2*i의 직사각형에서 타일을 놓을 수 있는 방법의 수 c라고 할 때.
dp[1] = 1;
dp[2] = 2;
인 것은 당연하다.
2*1의 직사각형이라면 2*1의 타일 하나밖에 안 들어가고.
2*2 라면 2*1을 두개 놓거나, 1*2를 두개 놓는 방법 두개가 존재한다.
2*n의 직사각형을 채우는 마지막 타일을 생각해보면. 두 가지중에 하나다
- 가로(2) 타일이 두 개 있는 경우
- 이 경우에, 마지막 타일을 제외한 나머지는 2*n-2 크기의 직사각형 문제와 같아지게 된다.
- 따라서,
dp[n-2]
와 같다.
- 세로(2) 타일이 한 개 있는 경우
- 이 경우에, 마지막 타일을 제외한 나머지는 2*n-1 크기의 직사각형 문제와 같아지게 된다.
- 따라서,
dp[n-1]
과 같다.
2×n 직사각형을 채우는 마지막 단계에서의 선택(1×2 타일 하나를 세로로 놓는 경우와 2×1 타일 두 개를 가로로 놓는 경우)은 서로 독립적이다. 즉, 마지막에 어떤 타일을 놓느냐에 따라 남은 공간을 채우는 방법이 결정된다.
따라서, dp[n-1] + dp[n-2]
는 이 모든 조합의 수를 고려한 식이다.
#include <iostream>
#include <vector>
using namespace std;
int dp[1001];
int main() {
int N;
cin >> N;
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= N; ++i) {
dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
}
cout << dp[N];
return 0;
}
어떤분이 공유해주신 SQL 함수 요약
문자열
CONCAT(str1, str2, ...): 문자열을 연결합니다.
SUBSTRING(str, start, length): 문자열에서 일부를 추출합니다.
UPPER(str), LOWER(str): 문자열을 대문자 또는 소문자로 변환합니다.
LENGTH(str), CHAR_LENGTH(str): 문자열의 길이 또는 글자 수를 반환합니다.
TRIM(str), LTRIM(str), RTRIM(str): 문자열 앞/뒤의 공백을 제거합니다.
REPLACE(str, old, new): 문자열에서 특정 부분을 다른 문자열로 대체합니다.
LEFT(str, length): 문자열의 왼쪽에서 일부를 추출합니다.
RIGHT(str, length): 문자열의 오른쪽에서 일부를 추출합니다.
LOCATE: 문자열에서 특정 부분의 위치를 찾습니다.
REVERSE(str) : 문자열을 역순으로 변환합니다.
SUBSTRING_INDEX(str, delimiter, count) : 문자열에서 특정 구분자(delimiter)를 기준으로 일부를 추출합니다.
FORMAT(number, decimals) : 숫자를 형식화하여 출력합니다.
LPAD(str, length, padstr) : 문자열을 왼쪽으로 패딩합니다.
RPAD(str, length, padstr) : 문자열을 오른쪽으로 패딩합니다.
수학
ROUND(num, decimals): 숫자를 반올림합니다.
ABS(num): 숫자의 절대값을 반환합니다.
CEIL(num), FLOOR(num): 숫자를 올림하거나 내림합니다.
SUM(column), AVG(column), MIN(column), MAX(column): 열의 합, 평균, 최소, 최대 값을 계산합니다.
시간
DATE_FORMAT(date, format) : 날짜 형식을 지정하여 반환합니다.
DATEDIFF(date1, date2) : 두 날짜 간의 일 수 차이를 반환합니다.
조건
IF(condition, true_value, false_value): 조건에 따라 값을 반환합니다.
CASE WHEN condition THEN result ELSE else_result END: 다양한 조건에 따라 다른 결과를 반환합니다.
집계
COUNT(column): 특정 열의 행 수를 반환합니다.
GROUP_CONCAT(column): 그룹 내에서 문자열을 결합합니다.
GROUP BY column: 그룹화 기준을 지정합니다.
HAVING condition: 그룹에 조건을 적용합니다.
집합끼리 연산
- UNION (합집합):
두 개의 SELECT 문의 결과를 합칩니다. 중복된 행은 하나만 나타납니다.
SELECT column_name FROM table1
UNION
SELECT column_name FROM table2;
- CROSS JOIN (카르티잔 곱):
두 테이블 간의 모든 가능한 조합을 반환합니다. 모든 행이 다른 테이블의 모든 행과 결합됩니다.
SELECT *
FROM table1
CROSS JOIN table2;
혹은
SELECT *
FROM table1, table2
- INNER JOIN:
두 테이블 간의 일치하는 행만 반환합니다.
SELECT *
FROM table1
INNER JOIN table2 ON table1.column_name = table2.column_name;
- LEFT JOIN (또는 LEFT OUTER JOIN):
왼쪽 테이블의 모든 행과 일치하는 오른쪽 테이블의 행을 반환합니다.
SELECT *
FROM table1
LEFT JOIN table2 ON table1.column_name = table2.column_name;
- RIGHT JOIN (또는 RIGHT OUTER JOIN):
오른쪽 테이블의 모든 행과 일치하는 왼쪽 테이블의 행을 반환합니다.
SELECT *
FROM table1
RIGHT JOIN table2 ON table1.column_name = table2.column_name;
- OUTER JOIN (또는 FULL OUTER JOIN):
왼쪽과 오른쪽 테이블 간의 모든 행을 반환하며, 일치하는 행이 없는 경우 NULL 값으로 표시됩니다.
SELECT *
FROM table1
LEFT JOIN table2 ON table1.column_name = table2.column_name
UNION
SELECT *
FROM table1
RIGHT JOIN table2 ON table1.column_name = table2.column_name;
변수 설정
- SET : 변수를 설정함(상수, 변수 모두 가능)
SET @my_variable = 10;
SET @my_variable = (SELECT column_name FROM my_table WHERE some_condition); - with : 서브 쿼리를 저장함
WITH cte_name AS (
SELECT column1, column2
FROM some_table
WHERE condition
)
필수 + 특이해보이는 문자열 공부한 거 정리한 겁니다. 다들 마지막 하루 화이팅임니다!
오늘은 시험 전 날.
Programmers 알고리즘 고득점 Kit의 이분탐색, 그래프 파트의 문제들과
지원대비 문제풀이집의 문제 일부를 풀었다.
또, 이전 기수의 출제 문제 유형들도 정리했다.
전체적으로 생각을 정리하는 시간을 가졌다.
빡세겐 공부하지 않았다. 오히려 독이 될 것 같아서..
내일은 일찍 일어나서 밥을 먹고, 문제 조금만 풀다가 시험 시작 !...
공부 계획
- 2월 20일 - SUM, MAX, MIN | GROUP BY 풀이 ✅ 2024-02-20
- 2월 21일 - IS NULL | JOIN 풀이 ✅ 2024-02-21
- 2월 22일 - String, Date 풀고 SQL 복습 및 PS 복습 ✅ 2024-02-22
- 2월 23일 - 최종 복습 및 컨디션 관리. (주변 오브젝트 정리 및 리허설) ✅ 2024-02-23
최종 평가
2024-02-23 학습 성과 평가서
고귀한 노력의 결실이 오늘 마감되었음을 확인하였습니다. 당신의 학습 여정은 마치 지식의 바다를 항해하는 여정과도 같았으며, 오늘의 기록은 그 여정의 마지막 항해일지가 되겠습니다.
### 학습 성과 요약
- **알고리즘과 자료 구조**: 구현, 그리디, 분할 정복, 완전 탐색, 유니온 파인드, 동적 프로그래밍, 너비 우선 탐색, 깊이 우선 탐색, 그리고 서로소 집합까지 다양한 알고리즘과 자료구조를 단단히 다져, 문제 해결 능력을 괄목할만하게 향상시켰습니다.
- **SQL 능력**: 복잡한 데이터 조작과 분석에 필요한 SQL 기술을 체계적으로 복습하고 실습함으로써, 데이터베이스 관리와 조작 능력을 대폭 강화하였습니다.
- **문제 해결 전략**: 시험 준비 과정에서 입국심사, 징검다리, 가장 먼 노드, 순위 등의 문제를 해결함으로써 이진 탐색과 그래프 탐색 알고리즘에 대한 깊은 이해를 바탕으로 문제 해결 전략을 다변화하였습니다.
### 상세 평가
- 당신의 학습 계획과 실행은 체계적이고 일관성이 있었습니다. 모든 예정된 학습 목표를 달성하였으며, 이는 매우 훌륭한 자기 주도 학습 능력을 보여줍니다.
- 알고리즘 문제 해결 능력에서 특히 눈부신 성장을 이루었습니다. 다양한 알고리즘을 활용하여 복잡한 문제들을 체계적으로 해결하는 전략을 개발하였습니다.
- SQL 기술에 대한 심화 학습과 복습을 통해, 데이터 분석 및 관리 역량을 실질적으로 강화하였습니다. 이는 데이터 주도 의사 결정에 필수적인 기술입니다.
- 시험 전 마지막 날의 학습은, 당신의 지식을 정리하고, 자신감을 구축하는 데 중점을 두었습니다. 이는 시험에 임하는 최적의 전략이었습니다.
### 종합 평가 및 점수
당신의 노력과 성취는 단순한 점수로 평가하기 어려운 깊이와 넓이를 가지고 있습니다. 그러나 학습 계획의 체계성, 알고리즘과 SQL에 대한 깊은 이해, 그리고 시험 준비 과정에서의 뛰어난 집중력을 고려할 때, **98/100**이라는 점수를 부여합니다.
### 향후 권장 사항
- **지속적인 학습**: 알고리즘과 프로그래밍 기술은 끊임없이 발전하고 있습니다. 최신 동향을 따라가며 지속적으로 학습하는 자세를 유지해 주시길 바랍니다.
- **실제 프로젝트 적용**: 이론적 지식을 실제 프로젝트에 적용해 보는 경험을 통해, 문제 해결 능력을 더욱 실전적으로 발전시킬 수 있습니다.
- **복습과 성찰**: 학습한 내용을 주기적으로 복습하고, 자신만의 방식으로 정리하며, 학습 과정에서의 성찰을 통해 깊이 있는 지식을 구축해 나가시길 바랍니다.
당신의 노력과 열정은 이미 충분히 빛나고 있습니다. 내일의 시험에서 최고의 성과를 이루시길 진심으로 기원합니다. 항상 자신감을 가지고 도전하십시오. 당신의 미래는 밝습니다.
'일일 스터디노트' 카테고리의 다른 글
240225: 시험대비 DP 집중 복습, Stack Queue DFS+BFS (0) | 2024.02.25 |
---|---|
220224: 1차 코딩테스트 종료 🚩 긍정적으로 마무리 (1) | 2024.02.24 |
240222: SQL - String, Date. 데카르트곱 CROSS JOIN. (D-1) (0) | 2024.02.23 |
240221: SQL - IS NULL, JOIN 끝. 원상 복구 문제 (0) | 2024.02.21 |
240220: SQL - GROUP BY, SUM 등 집계함수 KIT 풀이 끝. (0) | 2024.02.20 |