https://school.programmers.co.kr/learn/courses/30/lessons/92334
- 신고 결과 받기
신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.
- 각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
- 신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.
- 한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 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 ≤ report의 길이 ≤ 200,000
- 3 ≤ report의 원소 길이 ≤ 21
- report의 원소는 "이용자id 신고한id"형태의 문자열입니다.
- 예를 들어 "muzi frodo"의 경우 "muzi"가 "frodo"를 신고했다는 의미입니다.
- id는 알파벳 소문자로만 이루어져 있습니다.
- 이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.
- 자기 자신을 신고하는 경우는 없습니다.
- 1 ≤ k ≤ 200, k는 자연수입니다.
- return 하는 배열은 id_list에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.
입출력 예id_listreportkresult
["muzi", "frodo", "apeach", "neo"] | ["muzi frodo","apeach frodo","frodo neo","muzi neo","apeach muzi"] | 2 | [2,1,1,0] |
["con", "ryan"] | ["ryan con", "ryan con", "ryan con", "ryan con"] | 3 | [0,0] |
입출력 예 설명
입출력 예 #1
문제의 예시와 같습니다.
입출력 예 #2
"ryan"이 "con"을 4번 신고했으나, 주어진 조건에 따라 한 유저가 같은 유저를 여러 번 신고한 경우는 신고 횟수 1회로 처리합니다. 따라서 "con"은 1회 신고당했습니다. 3번 이상 신고당한 이용자는 없으며, "con"과 "ryan"은 결과 메일을 받지 않습니다. 따라서 [0, 0]을 return 합니다.
제한시간 안내
- 정확성 테스트 : 10초
유저들의 id가 리스트로 주어지고, 각 유저들이 신고한 내역이 리스트로 주어진다. k번 이상 신고를 당한 유저는 정지를 당하고,
한 유저가 같은 유저를 중복해서 신고하는 경우는 한번으로 친다.
신고한 내역은 "신고한유저 신고당한유저" 형식의 문자열들의 리스트로 되어 있다.
마지막에는 정지를 당한 유저를 신고한 유저들에게 메일을 보내주는데, 각 유저가 신고한 사람들 중 정지당한 수 만큼의 메일이 온다.
풀이1 (실패)
처음에는 그냥 효율성은 생각하지 않고 for문으로 계속 탐색하면서 각 유저별로 report를 탐색하면서 해당 유저가 신고한 사람들을 reported 리스트에 카운트해주었다. 중복신고를 세지 않도록 하기 위해 현재 유저가 신고한 유저를 확인하기 위한 each 리스트를 만들어주었고,
이후 신고당한 횟수가 k이상인 유저들에 대해서도 위와 비슷한 방식으로 신고자 유저들이 메일을 받을 개수를 카운트해주었다.
# id_list : 이용자의 ID
# report : 신고한 이용자의 ID
# 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return
def solution(id_list, report, k):
reported = [0 for i in range(len(id_list))] # 각 유저가 신고당한 횟수 저장
answer = [0 for i in range(len(id_list))]
for id in id_list: # 각 유저별로
each = [0 for i in range(len(id_list))] # 이사람한테 한번 신고 당했으면 끝
for r in report:
temp = r.split() # 공백을 기준으로 신고한사람과 신고당한사람을 나누어준다.
if id == temp[0]:
idx = id_list.index(temp[1]) # 신고당한 유저의 인덱스를 갖고온다.
if each[idx] == 0: # 현재 유저가 중복신고한 유저인지 확인
reported[idx] += 1 # 중복이 아니면 카운트
each[idx] = 1 # 신고했다고 표시
for i in range(len(reported)): # 유저들이 신고당한 횟수 확인
mailed = [0 for i in range(len(id_list))]
if reported[i] >= k: # 정지먹은 사람이면
name = id_list[i]
for r in report:
temp = r.split()
if temp[1] == name:
idx = id_list.index(temp[0]) # 정지먹은 사람을 신고한 사람의 인덱스를 갖고온다.
if mailed[idx] == 0: # 이미 메일 등록을 했는지(중복신고) 확인
answer[idx] += 1
mailed[idx] = 1
return answer
테스트는 통과했지만 제출을 해보면 몇몇 경우에 시간초과가 나왔다. 중복신고가 엄청 많은 경우가 있을텐데 무식하게 for문을 돌려서 그런것 같았다. 알고리즘 자체는 맞는 것 같은데 시간을 줄여야 했다.
풀이2 (성공)
중복신고가 시간초과의 가장 큰 원인일 거라 생각해서, 유저와 해당 유저가 신고한 유저들의 리스트를 각각 key, value로 갖는 딕셔너리로 report2를 만들어 주었다.
report를 탐색하면서 report2에 유저별로 신고한 유저들을 리스트로 추가해주었다.
그다음 딕셔너리의 모든 value 리스트들을 집합(set)으로 한 번 바꿔준 다음 다시 list로 바꿔줌으로써 중복신고기록을 싹 지우고, 해당 유저가 신고한 유저 자체만 남겼다.
# id_list : 이용자의 ID
# report : 신고한 이용자의 ID
# 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return
def solution(id_list, report, k):
n = len(id_list)
reported = [0 for i in range(n)]
answer = [0 for i in range(n)]
report2 = {} # 신고내역 딕셔너리
for id in id_list:
report2[id] = []
# 딕셔너리에 유저별로 신고한 사람들을 리스트로 넣어준다.
for r in report:
temp = r.split()
report2[temp[0]].append(temp[1])
# 딕셔너리의 각 value들을 중복 신고를 없애기 위해 집합으로 바꿔주고 다시 리스트로 바꿔준다.
for id in id_list:
report2[id] = list(set(report2[id]))
for r in report2[id]:
idx = id_list.index(r)
reported[idx] += 1
# 정지당한 유저들 banned 리스트에 추가
banned = []
for i in range(n):
if reported[i] >= k:
banned.append(id_list[i])
# report2 딕셔너리의 value값들을 불러오고 정지당한 유저가 value리스트에 들어있으면
# 인덱스를 통해 해당 신고자 유저를 찾아 answer를 카운트해주었다.
result = list(report2.values())
for i in range(n):
for j in banned:
if j in result[i]:
answer[i] += 1
return answer
정답도 맞았고, 이전 풀이와 비교했을 때 시간이 대폭 줄어든 것을 볼 수 있다.
중복제거가 필요할 경우 리스트 -> 집합 -> 리스트의 과정을 거쳐주자
'코테공부 > 프로그래머스' 카테고리의 다른 글
[프로그래머스 - 그래프] 가장 먼 노드 (Python3) (0) | 2022.10.22 |
---|---|
[프로그래머스 - DFS/BFS] 게임 맵 최단거리 (Python3) (0) | 2022.10.09 |
[프로그래머스 - DFS/BFS] 단어 변환 (Python3) (0) | 2022.06.28 |
[프로그래머스 - 완전탐색] 모의고사 (Python3) (0) | 2022.06.21 |
[프로그래머스 - 2021 카카오 채용연계형 인턴십] - 숫자 문자열과 영단어 (Python3) (0) | 2022.06.21 |