프로그래머스
SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프
programmers.co.kr
1. 문제
매운 것을 좋아하는 Leo는 모든 음식의 스코빌 지수를 K 이상으로 만들고 싶습니다. 모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 Leo는 스코빌 지수가 가장 낮은 두 개의 음식을 아래와 같이 특별한 방법으로 섞어 새로운 음식을 만듭니다.
섞은 음식의 스코빌 지수 = 가장 맵지 않은 음식의 스코빌 지수 + (두 번째로 맵지 않은 음식의 스코빌 지수 * 2)
Leo는 모든 음식의 스코빌 지수가 K 이상이 될 때까지 반복하여 섞습니다.
Leo가 가진 음식의 스코빌 지수를 담은 배열 scoville과 원하는 스코빌 지수 K가 주어질 때, 모든 음식의 스코빌 지수를 K
이상으로 만들기 위해 섞어야 하는 최소 횟수를 return 하도록 solution 함수를 작성해 주세요.
제한사항
- scoville의 길이는 2 이상 1,000,000 이하입니다.
- K는 0 이상 1,000,000,000 이하입니다.
- scoville의 원소는 각각 0 이상 1,000,000 이하입니다.
- 모든 음식의 스코빌 지수를 K 이상으로 만들 수 없는 경우에는 -1을 return 합니다.
입출력 예
| scoville | K | return | 
| [1, 2, 3, 9, 10, 12] | 7 | 2 | 
입출력 예 설명
입출력 예 #1
- 스코빌 지수가 1인 음식과 2인 음식을 섞으면 음식의 스코빌 지수가 아래와 같이 됩니다.
 새로운 음식의 스코빌 지수 = 1 + (2 * 2) = 5
 가진 음식의 스코빌 지수 = [5, 3, 9, 10, 12]
- 스코빌 지수가 3인 음식과 5인 음식을 섞으면 음식의 스코빌 지수가 아래와 같이 됩니다.
 새로운 음식의 스코빌 지수 = 3 + (5 * 2) = 13
 가진 음식의 스코빌 지수 = [13, 9, 10, 12]
모든 음식의 스코빌 지수가 7 이상이 되었고 이때 섞은 횟수는 2회입니다.
2. 오답
function solution(scoville, K) {
    let answer = 0;
    // dfs
    const dfs = (arr) => {
        answer++;
        // 배열을 [섞은 음식의 스코빌 지수, ...나머지 음식]으로 초기화
        arr = [arr[0] + (arr[1] * 2), ...arr.slice(2)];
        // 섞는 연산이 가능하고 기준 K를 넘지 못한 음식이 남아있으면 dfs 재귀 호출
        if (arr.length > 1 && arr.filter(val => val < K).length > 0) return dfs(arr.sort((a,b) => a-b));
        // 섞는 연산이 불가능하면 기준 K를 넘긴 음식의 배열을 반환
        return arr.filter(val => val >= K);
    }
    
    // 모든 음식이 기준 K를 넘었다면 dfs 탐색하지 않고 0 리턴
    if (scoville.filter(val => val < K).length < 1) return answer;
    // dfs에서 반환된 기준 K를 넘긴 음식의 배열 요소가 있으면
    // 섞은 횟수 answer를 반환, 없으면 -1을 반환
    return dfs(scoville.sort((a,b) => a-b)).length > 0 ? answer : -1;
}
dfs로 풀었더니 효율성 테스트에서 메모리 초과 오류가 발생한다.
어떤 방법으로 풀어야할지 감이 안 와서 다른 사람의 풀이를 찾아봐야겠다😓😓
3. 다른 사람 풀이
풀이 전략
- Heap 구조를 활용
// MinHeap 클래스를 정의하는 코드입니다. 이 클래스는 최소 힙을 구현합니다.
class MinHeap {
  // constructor는 클래스의 생성자 메서드로, 힙을 저장할 빈 배열 heap을 초기화합니다.
  constructor() {
    this.heap = [];
  }
  // getParentIndex 메서드는 주어진 인덱스의 부모 노드 인덱스를 계산하여 반환합니다.
  getParentIndex(index) {
    return Math.floor((index - 1) / 2);
    // 부모 노드의 인덱스는 (index - 1) / 2의 내림 값입니다.
  }
  // getLeftChildIndex 메서드는 주어진 인덱스의 왼쪽 자식 노드 인덱스를 계산하여 반환합니다.
  getLeftChildIndex(index) {
    return 2 * index + 1;
    // 왼쪽 자식 노드의 인덱스는 2 * index + 1입니다.
  }
  // getRightChildIndex 메서드는 주어진 인덱스의 오른쪽 자식 노드 인덱스를 계산하여 반환합니다.
  getRightChildIndex(index) {
    return 2 * index + 2;
    // 오른쪽 자식 노드의 인덱스는 2 * index + 2입니다.
  }
  // swap 메서드는 힙의 두 노드의 값을 교환합니다.
  swap(index1, index2) {
    const temp = this.heap[index1];
    this.heap[index1] = this.heap[index2];
    this.heap[index2] = temp;
    // 임시 변수 temp를 사용하여 index1의 값을 저장합니다.
    // 그리고 index1에 index2의 값을 할당한 후 index2에 temp의 값을 할당합니다.
  }
  // push 메서드는 새로운 요소를 힙에 삽입합니다.
  push(value) {
    this.heap.push(value);
    // currentIndex는 삽입된 요소의 인덱스이고, parentIndex는 부모 노드의 인덱스입니다.
    let currentIndex = this.heap.length - 1;
    let parentIndex = this.getParentIndex(currentIndex);
    // 삽입된 요소가 루트 노드가 아니고, 부모 노드의 값이 삽입된 요소의 값보다 크다면 교환합니다.
    // 교환 후에는 currentIndex를 parentIndex로 업데이트하고, 다시 parentIndex를 구합니다.
    while (
      currentIndex > 0 &&
      this.heap[parentIndex] > this.heap[currentIndex]
    ) {
      this.swap(currentIndex, parentIndex);
      currentIndex = parentIndex;
      parentIndex = this.getParentIndex(currentIndex);
    }
    // 이 과정을 반복하여 힙의 속성을 유지합니다.
  }
  // pop 메서드는 힙에서 가장 작은 요소(루트 노드)를 꺼내고 제거합니다.
  pop() {
    // 힙이 비어있다면 null을 반환합니다.
    if (this.isEmpty()) return null;
    // 힙에 요소가 하나밖에 없다면 해당 요소를 제거하고 반환합니다.
    if (this.heap.length === 1) return this.heap.pop();
    // 루트 노드의 값을 root 변수에 저장하고, 힙의 마지막 요소를 루트 노드로 이동시킵니다.
    const root = this.heap[0];
    this.heap[0] = this.heap.pop();
    // currentIndex는 현재 노드의 인덱스이고, leftChildIndex와 rightChildIndex는 각각 왼쪽과 오른쪽 자식 노드의 인덱스입니다.
    let currentIndex = 0;
    let leftChildIndex = this.getLeftChildIndex(currentIndex);
    let rightChildIndex = this.getRightChildIndex(currentIndex);
    // 현재 노드의 값이 왼쪽 자식이나 오른쪽 자식보다 크다면 교환이 필요합니다.
    while (
      (leftChildIndex < this.heap.length &&
        this.heap[currentIndex] > this.heap[leftChildIndex]) ||
      (rightChildIndex < this.heap.length &&
        this.heap[currentIndex] > this.heap[rightChildIndex])
    ) {
      // 왼쪽 자식과 오른쪽 자식 중 더 작은 값을 가진 노드와 교환합니다.
      const smallerChildIndex =
        rightChildIndex >= this.heap.length ||
        this.heap[leftChildIndex] < this.heap[rightChildIndex]
          ? leftChildIndex
          : rightChildIndex;
      this.swap(currentIndex, smallerChildIndex);
      // 교환 후에는 currentIndex를 교환한 자식 노드의 인덱스로 업데이트하고, 다시 leftChildIndex와 rightChildIndex를 구합니다.
      currentIndex = smallerChildIndex;
      leftChildIndex = this.getLeftChildIndex(currentIndex);
      rightChildIndex = this.getRightChildIndex(currentIndex);
      // 이 과정을 반복하여 힙의 속성을 유지합니다.
    }
    // 마지막으로 제거된 루트 노드의 값 root를 반환합니다.
    return root;
  }
  // isEmpty 메서드는 힙이 비어있는지 확인합니다.
  isEmpty() {
    // 힙의 길이가 0이면 true를 반환하고, 그렇지 않으면 false를 반환합니다.
    return this.heap.length === 0;
  }
}
// solution 함수는 주어진 스코빌 지수 배열 scoville과 목표 스코빌 지수 K를 받습니다.
// 그리고 모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 섞어야 하는 최소 횟수를 반환합니다.
function solution(scoville, K) {
  // 먼저 MinHeap 클래스의 인스턴스 minHeap을 생성합니다.
  const minHeap = new MinHeap();
  // for 반복문을 사용하여 scoville 배열의 모든 요소를 minHeap에 삽입합니다.
  for (let i = 0; i < scoville.length; i++) {
    minHeap.push(scoville[i]);
  }
  // count 변수를 초기화하여 섞은 횟수를 저장합니다.
  let count = 0;
  // while 반복문을 사용하여 minHeap의 루트 노드(가장 작은 스코빌 지수)가 K 미만인 동안 반복합니다.
  while (minHeap.heap[0] < K) {
    // 만약 minHeap에 요소가 1개밖에 없다?
    // 그러면 합할 개수 부족으로 모든 음식을 K 이상으로 만들 수 없으므로 -1을 반환합니다.
    if (minHeap.heap.length === 1) return -1;
    // minHeap에서 가장 작은 스코빌 지수 first와 두 번째로 작은 스코빌 지수 second를 꺼냅니다.
    const first = minHeap.pop();
    const second = minHeap.pop();
    // 섞은 음식의 스코빌 지수 mixed를 계산합니다.
    const mixed = first + second * 2;
    // mixed를 다시 minHeap에 삽입합니다.
    minHeap.push(mixed);
    // 섞은 횟수 count를 1 증가시킵니다.
    count++;
  }
  // while 반복문이 종료되면 모든 음식의 스코빌 지수가 K 이상이 된 것입니다.
  // 그러므로 섞은 횟수 count를 반환합니다.
  return count;
}
[프로그래머스] 주식가격 (LV2 - JavaScript)
프로그래머스SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프programmers.co.kr1. 문제 초 단위로 기록된 주식가격이 담긴 배열 prices가 매개변수로 주어
dev-hpk.tistory.com
[프로그래머스] 땅따먹기 (LV2 - JavaScript)
프로그래머스SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프programmers.co.kr1. 문제제한사항 행의 개수 N : 100,000 이하의 자연수열의 개수는 4개이고,
dev-hpk.tistory.com
'코딩 테스트 > 프로그래머스(LV2)' 카테고리의 다른 글
| [프로그래머스] 택배상자 (LV2 - JavaScript) (4) | 2024.12.11 | 
|---|---|
| [프로그래머스] 스킬트리 (LV2 - JavaScript) (4) | 2024.12.10 | 
| [프로그래머스] 주식가격 (LV2 - JavaScript) (4) | 2024.12.05 | 
| [프로그래머스] 땅따먹기 (LV2 - JavaScript) (3) | 2024.12.04 | 
| [프로그래머스] 뒤에 있는 큰 수 찾기 (LV2 - JavaScript) (3) | 2024.12.01 |