https://school.programmers.co.kr/learn/courses/30/lessons/118668
문제 설명
[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]
당신은 코딩 테스트를 준비하기 위해 공부하려고 합니다. 코딩 테스트 문제를 풀기 위해서는 알고리즘에 대한 지식과 코드를 구현하는 능력이 필요합니다.
알고리즘에 대한 지식은 알고력, 코드를 구현하는 능력은 코딩력이라고 표현합니다. 알고력과 코딩력은 0 이상의 정수로 표현됩니다.
문제를 풀기 위해서는 문제가 요구하는 일정 이상의 알고력과 코딩력이 필요합니다.
예를 들어, 당신의 현재 알고력이 15, 코딩력이 10이라고 가정해보겠습니다.
- A라는 문제가 알고력 10, 코딩력 10을 요구한다면 A 문제를 풀 수 있습니다.
- B라는 문제가 알고력 10, 코딩력 20을 요구한다면 코딩력이 부족하기 때문에 B 문제를 풀 수 없습니다.
풀 수 없는 문제를 해결하기 위해서는 알고력과 코딩력을 높여야 합니다. 알고력과 코딩력을 높이기 위한 다음과 같은 방법들이 있습니다.
- 알고력을 높이기 위해 알고리즘 공부를 합니다. 알고력 1을 높이기 위해서 1의 시간이 필요합니다.
- 코딩력을 높이기 위해 코딩 공부를 합니다. 코딩력 1을 높이기 위해서 1의 시간이 필요합니다.
- 현재 풀 수 있는 문제 중 하나를 풀어 알고력과 코딩력을 높입니다. 각 문제마다 문제를 풀면 올라가는 알고력과 코딩력이 정해져 있습니다.
- 문제를 하나 푸는 데는 문제가 요구하는 시간이 필요하며 같은 문제를 여러 번 푸는 것이 가능합니다.
당신은 주어진 모든 문제들을 풀 수 있는 알고력과 코딩력을 얻는 최단시간을 구하려 합니다.
초기의 알고력과 코딩력을 담은 정수 alp와 cop, 문제의 정보를 담은 2차원 정수 배열 problems가 매개변수로 주어졌을 때, 모든 문제들을 풀 수 있는 알고력과 코딩력을 얻는 최단시간을 return 하도록 solution 함수를 작성해주세요.
모든 문제들을 1번 이상씩 풀 필요는 없습니다. 입출력 예 설명을 참고해주세요.
제한사항
- 초기의 알고력을 나타내는 alp와 초기의 코딩력을 나타내는 cop가 입력으로 주어집니다.
- 0 ≤ alp,cop ≤ 150
- 1 ≤ problems의 길이 ≤ 100
- problems의 원소는 [alp_req, cop_req, alp_rwd, cop_rwd, cost]의 형태로 이루어져 있습니다.
- alp_req는 문제를 푸는데 필요한 알고력입니다.
- 0 ≤ alp_req ≤ 150
- cop_req는 문제를 푸는데 필요한 코딩력입니다.
- 0 ≤ cop_req ≤ 150
- alp_rwd는 문제를 풀었을 때 증가하는 알고력입니다.
- 0 ≤ alp_rwd ≤ 30
- cop_rwd는 문제를 풀었을 때 증가하는 코딩력입니다.
- 0 ≤ cop_rwd ≤ 30
- cost는 문제를 푸는데 드는 시간입니다.
- 1 ≤ cost ≤ 100
정확성 테스트 케이스 제한사항
- 0 ≤ alp,cop ≤ 20
- 1 ≤ problems의 길이 ≤ 6
- 0 ≤ alp_req,cop_req ≤ 20
- 0 ≤ alp_rwd,cop_rwd ≤ 5
- 1 ≤ cost ≤ 10
효율성 테스트 케이스 제한사항
- 주어진 조건 외 추가 제한사항 없습니다.
문제요점
알고력, 코딩력 1을 높이기 위해 1의 시간이 필요하다
모든 문제를 풀 수 있는 최단시간을 리턴해라
dp 풀이
알고력, 코딩력이 누적으로 늘어나기 때문에 dp가 떠올랐다.
dp의 요점: 순회하며 기존값과 새로운 값의 비교하여 갱신
이차배열 dp
- 모든 문제를 풀기위해 최대알고력, 최대코딩력을 구한다
- 이를 이용해 dp 이차배열을 만든다 (나는 row=알고력, col=코딩력으로 설정했다)
- dp를 순회하며 알고력, 코딩력을 1씩 늘려가며 최단시간을 갱신한다. 단 이 때 최대알고력, 최대코딩력을 넘지 않게 주의한다.
- 능력이 늘고 풀 수 있는 문제가 있으면 해당 문제를 풀고 시간을 갱신한다.
- 이런식으로 순회가 끝난 뒤 dp[ maxAlp][maxCop] 최단시간을 리턴한다.
// 알고력, 코딩력 1을 높이기 위해 1의 시간이 필요
// 누적 dp
function solution(alp, cop, problems) {
let maxAlp = alp;
let maxCop = cop;
problems.forEach(([alp, cop]) => {
maxAlp = Math.max(maxAlp, alp);
maxCop = Math.max(maxCop, cop);
});
const dp = Array.from({ length: maxAlp + 1 }, () =>
Array.from({ length: maxCop + 1 }, () => Infinity)
);
dp[alp][cop] = 0;
for (let currAlp = alp; currAlp <= maxAlp; ++currAlp) {
for (let currCop = cop; currCop <= maxCop; ++currCop) {
// 알고력1을 높였을때 최대필요 알고력을 넘지 않으면 알고력1을 높인 시간을 갱신
if (currAlp + 1 <= maxAlp) {
dp[currAlp + 1][currCop] = Math.min(
dp[currAlp + 1][currCop],
dp[currAlp][currCop] + 1
);
}
// 코딩력1을 높였을때 최대필요 코딩력을 넘지 않으면 코딩력1을 높인 시간을 갱신
if (currCop + 1 <= maxCop) {
dp[currAlp][currCop + 1] = Math.min(
dp[currAlp][currCop + 1],
dp[currAlp][currCop] + 1
);
}
// 풀 수 있는 문제가 있음 풀고 시간 갱신
problems.forEach(([alp_req, cop_req, alp_rwd, cop_rwd, cost]) => {
// 풀 수 있는 문제다
if (currAlp >= alp_req && currCop >= cop_req) {
// 문제를 풀어 점수를 얻어서
const next_alp = Math.min(currAlp + alp_rwd, maxAlp);
const next_cop = Math.min(currCop + cop_rwd, maxCop);
dp[next_alp][next_cop] = Math.min(
dp[currAlp][currCop] + cost,
dp[next_alp][next_cop]
);
}
});
}
}
return dp[maxAlp][maxCop];
}
다른 풀이
다익스트라 = 가중치 그래프 + 우선순위 큐
function solution(alp, cop, problems) {
var answer = 0;
// 최대 알고, 코딩력 구하기
let max_alp = alp;
let max_cop = cop;
problems.forEach(([req_alp, req_cop]) => {
max_alp = Math.max(max_alp, req_alp);
max_cop = Math.max(max_cop, req_cop);
});
// 초기 알고, 코딩력이 필요한 최대 알고, 코딩력보다 크면 바로 모든 문제를 풀 수 있다. ////////////////////
if (alp >= max_alp && cop >= max_cop) {
return 0;
}
const mq = new MinQueue();
const visited = Array.from({ length: 151 }, () =>
Array.from({ length: 151 }, () => false)
);
problems.push([0, 0, 1, 0, 1]);
problems.push([0, 0, 0, 1, 1]);
mq.enqueue({ cost: 0, alp, cop });
// while (mq.heap.length > 1) {
while (mq.heap.length - 1 !== 0) {
const { cost, alp, cop } = mq.dequeue();
if (visited[alp][cop]) {
continue;
}
visited[alp][cop] = true;
// 모든 문제를 풀수 있음
if (alp >= max_alp && cop >= max_cop) {
return cost;
}
problems.forEach(([req_alp, req_cop, plus_alp, plus_cop, plus_cost]) => {
if (alp >= req_alp && cop >= req_cop) {
// const newAlp = Math.min(alp + plus_alp, max_alp);
// const newCop = Math.min(cop + plus_cop, max_cop);
const newAlp = alp + plus_alp < max_alp ? alp + plus_alp : max_alp;
const newCop = cop + plus_cop < max_cop ? cop + plus_cop : max_cop; // math.max 지양
mq.enqueue({
cost: cost + plus_cost,
alp: newAlp,
cop: newCop,
});
}
});
}
answer = 0;
return answer;
}
// 다익스트라 우선순위 큐
class MinQueue {
constructor() {
this.heap = [null];
}
enqueue(data) {
const { cost, alp, cop } = data;
let currIndex = this.heap.length;
// 2개이상일때 재정렬
while (currIndex > 1) {
const parentIndex = Math.floor(currIndex / 2);
// 부모보다 자식이 더 작으면 swap
if (this.heap[parentIndex].cost > cost) {
this.heap[currIndex] = this.heap[parentIndex];
currIndex = parentIndex;
} else {
break;
}
}
this.heap[currIndex] = { cost, alp, cop };
}
dequeue() {
const min = this.heap[1];
if (this.heap.length > 2) {
this.heap[1] = this.heap.pop();
let currIndex = 1;
let leftChildIndex = currIndex * 2;
let rightChildIndex = currIndex * 2 + 1;
while (this.heap[leftChildIndex]) {
let smallerIndex = leftChildIndex;
if (
this.heap[rightChildIndex] &&
this.heap[leftChildIndex].cost > this.heap[rightChildIndex].cost
) {
smallerIndex = rightChildIndex;
}
if (this.heap[currIndex].cost > this.heap[smallerIndex].cost) {
[this.heap[currIndex], this.heap[smallerIndex]] = [
this.heap[smallerIndex],
this.heap[currIndex],
];
currIndex = smallerIndex;
} else {
break;
}
leftChildIndex = currIndex * 2;
rightChildIndex = currIndex * 2 + 1;
}
} else if (this.heap.length === 2) {
this.heap.pop();
} else {
return null;
}
return min;
}
}
느낀점
우선순위 큐는 js에서는 구현을 해야하고 배열상 인덱스 초과의 문제, 시간초과 등 문제가 많아서 개인적으로 dp가 더 간단하지만 강력한 풀이인 것 같다.
'알고리즘' 카테고리의 다른 글
비트마스킹 (0) | 2023.12.06 |
---|---|
[프로그래머스] 숫자 변환하기 (0) | 2023.12.06 |
우선순위 큐, 힙 (0) | 2023.11.22 |
[카카오 2022] 등산코스 정하기 (0) | 2023.11.18 |
[레벨2] n^2 배열 자르기 (0) | 2023.11.03 |