문제
신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.
- 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
- 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
- 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.
- k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
- 유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.
다음은 전체 유저 목록이 ["muzi", "frodo", "apeach", "neo"]이고, k = 2(즉, 2번 이상 신고당하면 이용 정지)인 경우의 예시입니다.
유저 ID | 유저가 신고한 ID | 설명 |
---|---|---|
"muzi" | "frodo" | "muzi"가 "frodo"를 신고했습니다. |
"apeach" | "frodo" | "apeach"가 "frodo"를 신고했습니다. |
"frodo" | "neo" | "frodo"가 "neo"를 신고했습니다. |
"muzi" | "neo" | "muzi"가 "neo"를 신고했습니다. |
"apeach" | "muzi" | "apeach"가 "muzi"를 신고했습니다. |
각 유저별로 신고당한 횟수는 다음과 같습니다.
유저 ID | 신고당한 횟수 |
---|---|
"muzi" | 1 |
"frodo" | 2 |
"apeach" | 0 |
"neo" | 2 |
위 예시에서는 2번 이상 신고당한 "frodo"와 "neo"의 게시판 이용이 정지됩니다. 이때, 각 유저별로 신고한 아이디와 정지된 아이디를 정리하면 다음과 같습니다.
유저 ID | 유저가 신고한 ID | 정지된 ID |
---|---|---|
"muzi" | ["frodo", "neo"] | ["frodo", "neo"] |
"frodo" | ["neo"] | ["neo"] |
"apeach" | ["muzi", "frodo"] | ["frodo"] |
"neo" | 없음 | 없음 |
따라서 "muzi"는 처리 결과 메일을 2회, "frodo"와 "apeach"는 각각 처리 결과 메일을 1회 받게 됩니다.
이용자의 ID가 담긴 문자열 배열 id_list
, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열 report
, 정지 기준이 되는 신고 횟수 k
가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.
제한사항
- 2 ≤
id_list
의 길이 ≤ 1,000- 1 ≤
id_list
의 원소 길이 ≤ 10 id_list
의 원소는 이용자의 id를 나타내는 문자열이며 알파벳 소문자로만 이루어져 있습니다.id_list
에는 같은 아이디가 중복해서 들어있지 않습니다.
- 1 ≤
- 1 ≤
report
의 길이 ≤ 200,000- 3 ≤
report
의 원소 길이 ≤ 21 report
의 원소는 "이용자id 신고한id"형태의 문자열입니다.- 예를 들어 "muzi frodo"의 경우 "muzi"가 "frodo"를 신고했다는 의미입니다.
- id는 알파벳 소문자로만 이루어져 있습니다.
- 이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.
- 자기 자신을 신고하는 경우는 없습니다.
- 3 ≤
- 1 ≤
k
≤ 200,k
는 자연수입니다. - return 하는 배열은
id_list
에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.
문제풀이
문제를 보고, 아래와 같은 해답으로 문제를 풀면 되겠다고 생각했지만 반복문이 많이 등장하는 것이 걱정이 되었다. 아니나 다를까, 문제를 제출해보니 24개의 테스트 중 2개의 테스트에서 시간 초과가 나왔다. 아래의 코드는 아래의 해답 기조는 유지하고 부분부분 약간씩 리펙토링 해 정답을 얻은 코드.
- 신고한 사람이 누구를 신고했는지 알 수 있는 리스트를 구한다.
Map<String, HashSet<String>>
을 이용해서 key 값에는 신고한 사람의 아이디를, value 값에는 그 사람이 신고한 사람들의 리스트(코드 상에서는 중복을 피하기 위해 set을 사용)를 담는다.
- 1번의 결과를 바탕으로 신고 받은 사람들의 신고 받은 횟수를 카운팅한다.
idList
에 존재하는 모든 회원을 한 번씩 호출하여 1번의 결과인userReportMap
에 존재하는 회원의 신고 횟수를 카운팅한다.- 이 때 유의할 점은, 자신이 다른 회원을 신고한 신고 리스트는 들여다 볼 필요가 없다는 것. 이 부분에 대한 예외 처리를 하지 않으면 테스트 24개 중 2개의 테스트에서 시간 초과가 발생한다.
- 2번의 결과를 바탕으로 계정의 이용이 정지된 사람의 리스트를 구한다. 그리고 이를
List<String>
에 담는다. - 계정 이용이 정지된 사람의 리스트(3번의 결과)와 각각의 회원이 신고한 회원의 정보가 있는 리스트(1번의 결과)를 바탕으로 각각의 회원이 메일을 받을 횟수를 구한다.
- 4번의 결과를 정답으로 제출.
import java.util.*;
class Solution {
public int[] solution(String[] idList, String[] report, int k) {
// 1. 신고한 사람의 신고 리스트를 추출한다.
Map<String, HashSet<String>> userReportMap = getUserReportMap(idList, report);
// 2. 신고 받은 사람의 수를 카운팅한다.
Map<String, Integer> reportCountMap = getReportCountMap(idList, userReportMap);
// 3. 계정 이용 정지된 사람을 구한다.
List<String> blockedUserList = getBlockedUserList(k, reportCountMap);
// 4. 정답을 제출한다. (계정 정지된 사람들과 회원이 신고한 리스트를 대조)
return getAnswer(idList, userReportMap, blockedUserList);
}
private Map<String, HashSet<String>> getUserReportMap(String[] idList, String[] report) {
// userReportMap을 만들고 초기화한다.
Map<String, HashSet<String>> userReportMap = new HashMap<>();
for (String user : idList) {
userReportMap.put(user, new HashSet<>()); // key : user 이름, value : user가 신고한 회원 리스트(실제로는 set이지만)
}
// 각각의 회원이 신고한 회원을 추출해 userReportMap에 넣는다.
for (String s : report) {
String fromWho = s.split(" ")[0];
String reportedUser = s.split(" ")[1];
HashSet<String> userReportSet = userReportMap.get(fromWho);
userReportSet.add(reportedUser);
}
return userReportMap;
}
private Map<String, Integer> getReportCountMap(String[] idList, Map<String, HashSet<String>> userReportMap) {
// 회원을 한 명씩 꺼내 그 회원이 총 몇 번 신고 당했는지 구해 getReportCountMap에 담는다.
Map<String, Integer> reportCountMap = new HashMap<>();
for (String s : idList) {
// 회원이 신고한 userReportMap을 가져와 자신이 신고당한 횟수를 카운팅한다.
int reportedCount = 0;
for (String key : userReportMap.keySet()) {
if (key.equals(s)) {
continue;
}
HashSet<String> reportSet = userReportMap.get(key);
for (String reportedUser : reportSet) {
// reportSet에는 자신의 이름이 있거나, 없거나 둘 중 하나.
// 따라서 순회 도중 자신의 이름이 발견되면 카운팅을 1 늘리고 해당 for문을 종료시킨다.
if (reportedUser.equals(s)) {
reportedCount++;
break;
}
}
}
// 신고 당한 횟수를 userCountMap에 담는다.
reportCountMap.put(s, reportedCount);
}
return reportCountMap;
}
private List<String> getBlockedUserList(int k, Map<String, Integer> reportCountMap) {
// 계정 이용 정지된 회원들만 따로 담은 리스트
List<String> blockedUserList = new ArrayList<>();
for (String s : reportCountMap.keySet()) {
// 신고당한 횟수가 k 보다 크거가 같다면 blockedUserList에 추가
if (reportCountMap.get(s) >= k) {
blockedUserList.add(s);
}
}
return blockedUserList;
}
private int[] getAnswer(String[] idList, Map<String, HashSet<String>> userReportMap, List<String> blockedUserList) {
int[] answer = new int[idList.length];
for (int i = 0; i < idList.length; i++) {
String user = idList[i];
HashSet<String> reportSetOfUser = userReportMap.get(user);
for (String reportOfUser : reportSetOfUser) {
for (String blockedUser : blockedUserList) {
if (reportOfUser.equals(blockedUser)) {
answer[i] += 1;
break;
}
}
}
}
return answer;
}
}
느낀 점
위의 답안에는 for문이 정말 많다. 그만큼 시간 복잡도도 높을 것이다(위의 답안에는 심지어 3중 for문까지 존재한다... 말 다했지 뭐....). 문제를 맞추고 다른 사람의 풀이를 봤을 때 멘붕이 왔다. 내 답안은 딱 100줄인데, 30-40줄 내외로 문제를 해결한 사람들이 많았기 때문. 자바 11을 사용하면서 자바 8의 문법인 스트림, 람다 등을 사용하지 못한 내 코드들에 대해 약간의 반성을 느낀 문제였다.
첨언. 아마 level 1 이었기 때문에 이 답안이 테스트에서 통과할 수 있었으리라 생각한다. 더 정진하자.
'코딩 문제' 카테고리의 다른 글
[프로그래머스 level 3] 연속 펄스 부분 수열의 합 - Java (0) | 2023.03.18 |
---|---|
[프로그래머스 level 2] 쿼드압축 후 개수 세기 - Java (0) | 2023.01.31 |
[백준 1018번] 체스판 다시 칠하기 - Java (0) | 2022.02.06 |
[백준 1929번] 소수 구하기 (에라토스테네스의 체) - Java (0) | 2022.02.02 |
[프로그래머스 level 2] 다리를 지나는 트럭 - Java (0) | 2021.12.27 |