Algorithm/코딩테스트

[프로그래머스][JAVA] 베스트앨범

ioh'sDeveloper 2024. 5. 22. 00:45
💡 문제
베스트앨범 (https://school.programmers.co.kr/learn/courses/30/lessons/42579)
자세한 문제 설명과 입출력 예는 링크를 참고해주세요.

 

📝 선행 개념

1. Comparable 인터페이스

public interface Comparable<T> {
    public int compareTo(T o);
}

이 메서드는 현재 객체와 지정된 객체를 비교하여 다음과 같은 값을 반환해야 합니다

2. Collections.sort

Collections.sort 메서드는 Java의 Collections 클래스에 정의된 정적 메서드로, 리스트를 정렬할 때 사용됩니다. Comparable 인터페이스를 구현한 객체들의 리스트를 정렬할 때 유용합니다.

List<Song> songs = new ArrayList<>();
songs.add(new Song(0, 500));
songs.add(new Song(1, 600));
songs.add(new Song(2, 150));

Collections.sort(songs); // Song 클래스의 compareTo 메서드 기준으로 정렬

위의 예제에서 Collections.sort(songs) songs 리스트를 Song 클래스의 compareTo 메서드 기준으로 정렬합니다.

🤓 문제 풀이

  1. 각 장르의 총 재생 횟수를 계산하여 많이 재생된 장르 순으로 정렬
  2. 각 장르 내에서 노래를 재생 횟수 내림차순으로 정렬하고, 재생 횟수가 같은 경우 고유 번호 오름차순으로 정렬
  3. 렬된 정보를 바탕으로 각 장르에서 상위 두 개의 노래를 선택하여 결과값 구성
/*
Comparable을 이용한 정렬:
Collections.sort(songs)를 사용하여 각 장르별 노래 리스트를 정렬합니다.
keySet 리스트를 정렬하여 재생 횟수가 많은 순서대로 장르를 처리합니다.
 */
public class PRO_Hash_42579_3 {
    // Song 클래스 정의, Comparable 인터페이스를 구현하여 정렬 기준을 설정
    public static class Song implements Comparable<Song> {
        /*
        Song 클래스:
        Song 클래스는 각 노래의 인덱스와 재생 횟수를 저장하며, Comparable<Song>을 구현하여 정렬 기준을 정의합니다.
        compareTo 메서드는 재생 횟수를 기준으로 내림차순 정렬하고, 재생 횟수가 같으면 인덱스를 기준으로 오름차순 정렬합니다.
         */
        int index; // 노래의 인덱스
        int plays; // 노래의 재생 횟수

        public Song(int index, int plays) {
            this.index = index; // 인덱스 초기화
            this.plays = plays; // 재생 횟수 초기화
        }

        @Override
        public int compareTo(Song other) {
            // 재생 횟수가 같으면 인덱스 오름차순 정렬
            if (this.plays == other.plays) {
                return this.index - other.index;
            }
            // 재생 횟수가 다르면 재생 횟수 내림차순 정렬
            return other.plays - this.plays;
        }
    }

    public static int[] getBestAlbum(String[] genres, int[] plays) {
        /*
        자료 구조 업데이트:
        music 맵은 HashMap<String, List<Song>> 형태로 변경되어 장르별 노래 리스트를 저장합니다.
         */
        ArrayList<Integer> answer = new ArrayList<>(); // 최종 답을 저장할 리스트

        HashMap<String, Integer> num = new HashMap<>(); // 장르별 총 재생 횟수를 저장할 맵
        HashMap<String, List<Song>> music = new HashMap<>(); // 장르별 노래 리스트를 저장할 맵

        for (int i = 0; i < plays.length; i++) {
            // 장르별 총 재생 횟수를 갱신
            num.put(genres[i], num.getOrDefault(genres[i], 0) + plays[i]);

            // 장르별 노래 리스트에 현재 노래 추가
            if (!music.containsKey(genres[i])) {
                music.put(genres[i], new ArrayList<>()); // 해당 장르가 처음 등장하면 리스트 생성
            }
            music.get(genres[i]).add(new Song(i, plays[i])); // 노래 추가
        }

        // 장르별 총 재생 횟수를 기준으로 장르를 내림차순 정렬
        List<String> keySet = new ArrayList<>(num.keySet());
        Collections.sort(keySet, (s1, s2) -> num.get(s2) - num.get(s1));

        for (String key : keySet) {
            List<Song> songs = music.get(key); // 현재 장르의 노래 리스트 가져오기
            Collections.sort(songs); // 노래 리스트를 재생 횟수 기준으로 정렬

            // 정렬된 리스트에서 상위 두 개의 노래 인덱스 추가
            answer.add(songs.get(0).index);
            if (songs.size() > 1) {
                answer.add(songs.get(1).index);
            }
        }

        // ArrayList를 int 배열로 변환하여 반환
        return answer.stream().mapToInt(i -> i).toArray();
    }
}

📚 풀이 보완

처음 문제를 풀었을 때와는 다른 방법으로 사용했다.
노래의 인덱스와 재생 횟수를 정의하는 Song 클래스를 정의하였고 Comparable<Song> 인터페이스를 사용하여 정렬 기준을 정의하여 재생 횟수 내림차순으로 정렬하고, 재생 횟수가 같은 경우 인덱스 오름차순으로 정렬하는 로직으로 변경하였다.

변경 전과 변경 후는 차이가 크지는 않았지만 Comparable 인터페이스를 구현하여 정렬을 더 직관적이고 효율적으로 수행할 수 있게 되었고 HashMap<String, List<Song>> 구조로 변경하여 장르별 노래 리스트를 보다 효율적으로 관리하고 정렬하는 코드로 작성할 수 있었다.


📍 Plus+

문제 상세 풀이 및 이모저모 ~..~

  1. Song 클래스 정의
public static class Song implements Comparable<Song> {
    int index; // 노래의 인덱스
    int plays; // 노래의 재생 횟수

    public Song(int index, int plays) {
        this.index = index; // 인덱스 초기화
        this.plays = plays; // 재생 횟수 초기화
    }

    @Override
    public int compareTo(Song other) {
        // 재생 횟수가 같으면 인덱스 오름차순 정렬
        if (this.plays == other.plays) {
            return this.index - other.index;
        }
        // 재생 횟수가 다르면 재생 횟수 내림차순 정렬
        return other.plays - this.plays;
    }
}
  1. Comparable 인터페이스는 객체의 자연 정렬 순서를 정의하기 위해 사용, 구현 클래스는 compareTo 메서드를 오버라이드하여 두 객체의 비교 방법을 지정
  2. 위의 Song 클래스에서 compareTo 메서드는 재생 횟수가 같을 경우 인덱스를 기준으로 오름차순 정렬하고, 재생 횟수가 다를 경우 재생 횟수를 기준으로 내림차순 정렬합니다.
  3. Collections.sort
    Collections.sort 메서드는 리스트를 정렬
    리스트에 있는 객체들이 Comparable 인터페이스를 구현하고 있으면, compareTo 메서드에 정의된 기준에 따라 정렬
  4. HashMap과 getOrDefault
    HashMap 클래스의 getOrDefault 메서드는 키가 존재하면 해당 키의 값을 반환하고, 존재하지 않으면 기본값을 반환하는 메서드
    위의 코드에서 "classic" 키가 genrePlays 맵에 없으므로 getOrDefault는 0을 반환합니다.
  • For문과 getOrDefault를 사용한 총 재생 횟수 계산 총 재생 횟수를 계산하기 위해 for 문과 getOrDefault 메서드를 사용할 수 있습니다. 각 장르의 총 재생 횟수를 계산할 때 유용합니다.
    String[] genres = {"classic", "pop", "classic", "classic", "pop"};
    int[] plays = {500, 600, 150, 800, 2500};
    
    HashMap<String, Integer> genreTotalPlays = new HashMap<>();
    for (int i = 0; i < genres.length; i++) {
        genreTotalPlays.put(genres[i], genreTotalPlays.getOrDefault(genres[i], 0) + plays[i]);
    }
    위의 코드는 각 장르별 총 재생 횟수를 genreTotalPlays 맵에 저장합니다. getOrDefault 메서드를 사용하여 기본값 0을 설정하고, 현재 재생 횟수를 더합니다.