Skip to main content

[Java] 138475 귤 고르기


1.

📝 문제

명예의 전당(1)

고르기

2.

1. 접근방법

2.1문제 우선순위큐

image.png

image.png

image.png

출처: https://www.youtube.com/watch?app=desktop&v=AjFlp951nz0

위 그림처럼 힙은 부모-자식 노드 간 우선순위가 유지된 완전이진트리이다.
이 2가지를 만족하면 루트에는 항상 우선순위가 가장 높은 데이터가 있다.

배열을 이렇게 취급하면 된다.요약

  • 루트는 [1]에 있다.
  • N개의 데이터는 [1]~[N]에 차례로 존재한다.
  • 현재 노드 i를 기준, 왼쪽 자식 노드: [2i], 오른쪽 자식 노드: [2*i+1] 에 있다

2.2 힙 - 우선순위큐 구조

image.png

출처: 괭이쟁이님 github블로그

  • 항상 트리의 마지막에 데이터k개추가
  • 추가한 데이터가 부모 노드보다 우선순위가 높으면 교환
  • 루트이거나 부모 노드보다 낮을때까지 2를 반복
  • 힙은 완전이진트리 때문에 높이는 H = logN 이며, 최악의 경우 루트까지 교환하는 것이므로 시간복잡도는 O(logN)

image.png

  1. 루트 삭제하고 마지막 노드를 루트로 옮긴다.
  2. 두 자식노드 중 우선순위가 큰 노드보다 현재 노드가 우선순위가 작으면 교환한다.
  3. 현재노드가 우선순위가 가장 클까지 2를 반복한다.

2.3 우선순위 큐 문제

일반 큐(Queue)는 FIFO 구조라 먼저 들어온게 먼저 나가지만 우선순위큐는 우선순위크기높은 요소가 먼저 나간다.
Java에는 PriorityQueue 클래스가 있고, 내부적으힙(Heap)자료로다른 구현돼종류의 있어서수의 삽입/삭제가 O(log n)으로 효율적이다.

코테에서 대표적으로 쓰이는 상황은 아래와 같다.

  1. 소/최대값을 반복적으로 빠르게 뽑아야 할 때
    → 명예의 전당 문제는 계속 점수가 들어오고, 그 중 가장 낮은 점수를 즉시 알아야 함return
  2. 실시간 데이터 처리
    → 스트리밍 데이터에서 상위 K개 유지하기
  3. 그리디 알고리즘에서 항상 작은(또는 큰) 값부터 선택하기
    → 다익스트라 알고리즘(최단 경로) → 현재 가장 짧은 경로 노드부터 꺼내야 함
    → 허프만 코딩 → 가장 빈도가 낮은 문자 2개를 반복적으로 꺼내기

3.

2. 정답코드

우선순위큐 사용한 코드

import java.util.*;

class Solution {
    public int[]int solution(int k, int[] score)tangerine) {
        int[]HashMap<Integer, answerInteger> countMap = new int[score.length];
        PriorityQueue<Integer> pq = new PriorityQueueHashMap<>(); // 최소 힙
        for (int i = 0; i < score.tangerine.length; i++) {
            pq.add(score[countMap.put(tangerine[i]);, //countMap.getOrDefault(tangerine[i], 오늘0) 점수+ 추가
            
            // k개 넘으면 가장 낮은 점수 제거
            if (pq.size() > k) {
                pq.poll()1);
        }
        
        // 현재 명예의 전당에서 가장 낮은 점수 
            answer[i] = pq.peek();
        }
        
        return answer;
    }
}
  1. 매번 점수를 넣고
  2. 정렬한 뒤
  3. k개까지만 유지
  4. 가장 낮은 점수 answer[i]에 넣기

내가 푼 코드

생각나는 대로 풀었는데 ArrayList + sort를 사용했다. 이렇게 하면 매일 정렬하기 때문에 조금 비효율적이라고 한다. 코드는 돌아가는데 원칙적으로는 PriorityQueue(우선순위큐)를 쓰면 삽입/삭제가 더 효율적이라고 한다.
보통 정렬 알고리즘의 시간 복잡도는 O(n log n)이다. 입력 크기(n)만큼 다 돌면서 그 안에서 log n만큼 비교하는 구조이다. 이렇게 하면 입력이 커질수록 시간 복잡도가 증가하고 매우 느리다. 반면 이진 탐색이나 힙에서 삽입/삭제 하는 경우 시간 복잡도는 O(log n)이다. 입력 크기 n이 커져도 거의 증가하지 않는다. 입력이 백만 배 커져도 연산 횟수가 20배 정도밖에 차이 안 난다.

import java.util.*;

class Solution {
    public int[] solution(int k, int[] score) {
        // k일 다음부터는 출연가수 점수가 기존 명전 가수 k번째보다 높으면 너나가
        // 매일 가장 점수가 낮은 사람을 리턴
        int[] answer = new int[score.length];
        ArrayListList<Integer> rankListcounts = new ArrayList<>(countMap.values());
        //counts.sort(Collections.reverseOrder());
        
        그럼int 삽입picked 삭제가= 자유로운0;
        int -kinds = 0;
        for (int c : counts) {
            picked += c;
            kinds++;
            if (picked >= ArrayListk) //break;
        길이}
        
        k의return ArrayList만들kinds;
    }
}

3. 설명

  1. 해시맵에 귤의 종류, 개수를 매핑한다.
  2. 귤의 개수를 담을 List count를 선언하 // 내림차순 정렬한다.
  3. 고른 개수, 종류를 담을 변수 picked, kinds 를 선언한다.
  4. count를 하나씩 순회하며 picked에 갯수를 누적하고, 종류+1을 한다.
  5. 만약 고른 귤 개수 낮은k보다 크 리턴같으면 for멈춘다.

4. 오답 / 어려웠던 점

  • ArrayList만들고 map의 밸류와 매핑하는 법 new ArrayList<>(int i = 0; i < score.length; i++countMap.values() { rankList.add(score[i]);
  • 오름차순 정렬 Collections.sort(rankList, Collections.reverseOrder()); // counts)
  • 내림차순 정렬 // 이 때 k개씩 끊은거 어떻게 처리? // rankList 길이가 k보다 크면 if (rankList.size(counts.sort(Collections.reverseOrder() > k) { rankList.remove(rankList.size() - 1); // 제일 낮은 점수 제거하기 } // 현재 명예의 전당에서 가장 낮은 점수 answer[i] = rankList.get(rankList.size() - 1); } return answer; } }